diff --git a/Content.Client/GameObjects/Components/ComputerBoundUserInterface.cs b/Content.Client/GameObjects/Components/ComputerBoundUserInterface.cs new file mode 100644 index 0000000000..76d8bf0995 --- /dev/null +++ b/Content.Client/GameObjects/Components/ComputerBoundUserInterface.cs @@ -0,0 +1,78 @@ +using System; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Timing; + +namespace Content.Client.GameObjects.Components +{ + /// + /// ComputerBoundUserInterface shunts all sorts of responsibilities that are in the BoundUserInterface for architectural reasons into the Window. + /// NOTE: Despite the name, ComputerBoundUserInterface does not and will not care about things like power. + /// + public class ComputerBoundUserInterface : ComputerBoundUserInterfaceBase where W : BaseWindow, IComputerWindow, new() where S : BoundUserInterfaceState + { + [Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!; + private W? _window; + + protected override void Open() + { + base.Open(); + + _window = (W) _dynamicTypeFactory.CreateInstance(typeof(W)); + _window.SetupComputerWindow(this); + _window.OnClose += Close; + _window.OpenCentered(); + } + + // Alas, this constructor has to be copied to the subclass. :( + public ComputerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {} + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (_window == null) + { + return; + } + + _window.UpdateState((S) state); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _window?.Dispose(); + } + } + } + + /// + /// This class is to avoid a lot of <> being written when we just want to refer to SendMessage. + /// We could instead qualify a lot of generics even further, but that is a waste of time. + /// + public class ComputerBoundUserInterfaceBase : BoundUserInterface + { + public ComputerBoundUserInterfaceBase(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {} + public void SendMessage(BoundUserInterfaceMessage msg) + { + base.SendMessage(msg); + } + } + + public interface IComputerWindow + { + void SetupComputerWindow(ComputerBoundUserInterfaceBase cb) {} + void UpdateState(S state) {} + } +} + diff --git a/Content.Client/GameObjects/Components/Power/SolarControlWindow.xaml b/Content.Client/GameObjects/Components/Power/SolarControlWindow.xaml new file mode 100644 index 0000000000..a51f57154c --- /dev/null +++ b/Content.Client/GameObjects/Components/Power/SolarControlWindow.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + diff --git a/Content.Client/GameObjects/Components/Power/SolarControlWindow.xaml.cs b/Content.Client/GameObjects/Components/Power/SolarControlWindow.xaml.cs new file mode 100644 index 0000000000..5ced2ae49d --- /dev/null +++ b/Content.Client/GameObjects/Components/Power/SolarControlWindow.xaml.cs @@ -0,0 +1,164 @@ +using System; +using JetBrains.Annotations; +using Content.Shared.Solar; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Timing; + +namespace Content.Client.GameObjects.Components.Power +{ + [GenerateTypedNameReferences] + public sealed partial class SolarControlWindow : SS14Window, IComputerWindow + { + private SolarControlConsoleBoundInterfaceState _lastState = new(0, 0, 0, 0); + + public SolarControlWindow() + { + RobustXamlLoader.Load(this); + } + + public void SetupComputerWindow(ComputerBoundUserInterfaceBase cb) + { + PanelRotation.OnTextEntered += (text) => { + double value; + if (double.TryParse(text.Text, out value)) + { + SolarControlConsoleAdjustMessage msg = new SolarControlConsoleAdjustMessage(); + msg.Rotation = Angle.FromDegrees(value); + msg.AngularVelocity = _lastState.AngularVelocity; + cb.SendMessage(msg); + // Predict this... + _lastState.Rotation = msg.Rotation; + NotARadar.UpdateState(_lastState); + } + }; + PanelVelocity.OnTextEntered += (text) => { + double value; + if (double.TryParse(text.Text, out value)) + { + SolarControlConsoleAdjustMessage msg = new SolarControlConsoleAdjustMessage(); + msg.Rotation = NotARadar.PredictedPanelRotation; + msg.AngularVelocity = Angle.FromDegrees(value / 60); + cb.SendMessage(msg); + // Predict this... + _lastState.Rotation = NotARadar.PredictedPanelRotation; + _lastState.AngularVelocity = msg.AngularVelocity; + NotARadar.UpdateState(_lastState); + } + }; + } + + private string FormatAngle(Angle d) + { + return d.Degrees.ToString("F1"); + } + + // The idea behind this is to prevent every update from the server + // breaking the textfield. + private void UpdateField(LineEdit field, string newValue) + { + if (!field.HasKeyboardFocus()) + { + field.Text = newValue; + } + } + + public void UpdateState(SolarControlConsoleBoundInterfaceState scc) + { + _lastState = scc; + NotARadar.UpdateState(scc); + OutputPower.Text = ((int) MathF.Floor(scc.OutputPower)).ToString(); + SunAngle.Text = FormatAngle(scc.TowardsSun); + UpdateField(PanelRotation, FormatAngle(scc.Rotation)); + UpdateField(PanelVelocity, FormatAngle(scc.AngularVelocity * 60)); + } + + } + + public sealed class SolarControlNotARadar : Control + { + // This is used for client-side prediction of the panel rotation. + // This makes the display feel a lot smoother. + private IGameTiming _gameTiming = IoCManager.Resolve(); + + private SolarControlConsoleBoundInterfaceState _lastState = new(0, 0, 0, 0); + + private TimeSpan _lastStateTime = TimeSpan.Zero; + + public const int StandardSizeFull = 290; + public const int StandardRadiusCircle = 140; + public int SizeFull => (int) (StandardSizeFull * UIScale); + public int RadiusCircle => (int) (StandardRadiusCircle * UIScale); + + public SolarControlNotARadar() + { + MinSize = (SizeFull, SizeFull); + } + + public void UpdateState(SolarControlConsoleBoundInterfaceState ls) + { + _lastState = ls; + _lastStateTime = _gameTiming.CurTime; + } + + public Angle PredictedPanelRotation => _lastState.Rotation + (_lastState.AngularVelocity * ((_gameTiming.CurTime - _lastStateTime).TotalSeconds)); + + protected override void Draw(DrawingHandleScreen handle) + { + var point = SizeFull / 2; + var fakeAA = new Color(0.08f, 0.08f, 0.08f); + var gridLines = new Color(0.08f, 0.08f, 0.08f); + var panelExtentCutback = 4; + var gridLinesRadial = 8; + var gridLinesEquatorial = 8; + + // Draw base + handle.DrawCircle((point, point), RadiusCircle + 1, fakeAA); + handle.DrawCircle((point, point), RadiusCircle, Color.Black); + + // Draw grid lines + for (var i = 0; i < gridLinesEquatorial; i++) + { + handle.DrawCircle((point, point), (RadiusCircle / gridLinesEquatorial) * i, gridLines, false); + } + + for (var i = 0; i < gridLinesRadial; i++) + { + Angle angle = (Math.PI / gridLinesRadial) * i; + var aExtent = angle.ToVec() * RadiusCircle; + handle.DrawLine((point, point) - aExtent, (point, point) + aExtent, gridLines); + } + + // The rotations need to be adjusted because Y is inverted in Robust (like BYOND) + Vector2 rotMul = (1, -1); + // Hotfix corrections I don't understand + Angle rotOfs = new Angle(Math.PI * -0.5); + + Angle predictedPanelRotation = PredictedPanelRotation; + + var extent = (predictedPanelRotation + rotOfs).ToVec() * rotMul * RadiusCircle; + Vector2 extentOrtho = (extent.Y, -extent.X); + handle.DrawLine((point, point) - extentOrtho, (point, point) + extentOrtho, Color.White); + handle.DrawLine((point, point) + (extent / panelExtentCutback), (point, point) + extent - (extent / panelExtentCutback), Color.DarkGray); + + var sunExtent = (_lastState.TowardsSun + rotOfs).ToVec() * rotMul * RadiusCircle; + handle.DrawLine((point, point) + sunExtent, (point, point), Color.Yellow); + } + } + + [UsedImplicitly] + public class SolarControlConsoleBoundUserInterface : ComputerBoundUserInterface + { + public SolarControlConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {} + } +} diff --git a/Content.Client/Solar/SolarControlConsoleBoundUserInterface.cs b/Content.Client/Solar/SolarControlConsoleBoundUserInterface.cs deleted file mode 100644 index ed457181ec..0000000000 --- a/Content.Client/Solar/SolarControlConsoleBoundUserInterface.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using Content.Shared.Solar; -using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; -using Robust.Shared.Timing; - -namespace Content.Client.Solar -{ - [UsedImplicitly] - public class SolarControlConsoleBoundUserInterface : BoundUserInterface - { - [Dependency] private readonly IGameTiming _gameTiming = default!; - - private SolarControlWindow? _window; - private SolarControlConsoleBoundInterfaceState _lastState = new(0, 0, 0, 0); - - protected override void Open() - { - base.Open(); - - _window = new SolarControlWindow(_gameTiming); - _window.OnClose += Close; - _window.PanelRotation.OnTextEntered += (text) => { - double value; - if (double.TryParse(text.Text, out value)) - { - SolarControlConsoleAdjustMessage msg = new SolarControlConsoleAdjustMessage(); - msg.Rotation = Angle.FromDegrees(value); - msg.AngularVelocity = _lastState.AngularVelocity; - SendMessage(msg); - } - }; - _window.PanelVelocity.OnTextEntered += (text) => { - double value; - if (double.TryParse(text.Text, out value)) - { - SolarControlConsoleAdjustMessage msg = new SolarControlConsoleAdjustMessage(); - msg.Rotation = _lastState.Rotation; - msg.AngularVelocity = Angle.FromDegrees(value / 60); - SendMessage(msg); - } - }; - _window.OpenCentered(); - } - - public SolarControlConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) - { - } - - private string FormatAngle(Angle d) - { - return d.Degrees.ToString("F1"); - } - - // The idea behind this is to prevent every update from the server - // breaking the textfield. - private void UpdateField(LineEdit field, string newValue) - { - if (!field.HasKeyboardFocus()) - { - field.Text = newValue; - } - } - - protected override void UpdateState(BoundUserInterfaceState state) - { - base.UpdateState(state); - - if (_window == null) - { - return; - } - - SolarControlConsoleBoundInterfaceState scc = (SolarControlConsoleBoundInterfaceState) state; - _lastState = scc; - _window.NotARadar.UpdateState(scc); - _window.OutputPower.Text = ((int) MathF.Floor(scc.OutputPower)).ToString(); - _window.SunAngle.Text = FormatAngle(scc.TowardsSun); - UpdateField(_window.PanelRotation, FormatAngle(scc.Rotation)); - UpdateField(_window.PanelVelocity, FormatAngle(scc.AngularVelocity * 60)); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - _window?.Dispose(); - } - } - - private sealed class SolarControlWindow : SS14Window - { - public readonly Label OutputPower; - public readonly Label SunAngle; - - public readonly SolarControlNotARadar NotARadar; - - public readonly LineEdit PanelRotation; - public readonly LineEdit PanelVelocity; - - public SolarControlWindow(IGameTiming igt) - { - Title = "Solar Control Window"; - - var rows = new GridContainer(); - rows.Columns = 2; - - // little secret: the reason I put the values - // in the first column is because otherwise the UI - // layouter autoresizes the window to be too small - rows.AddChild(new Label {Text = "Output Power:"}); - rows.AddChild(new Label {Text = ""}); - - rows.AddChild(OutputPower = new Label {Text = "?"}); - rows.AddChild(new Label {Text = "W"}); - - rows.AddChild(new Label {Text = "Sun Angle:"}); - rows.AddChild(new Label {Text = ""}); - - rows.AddChild(SunAngle = new Label {Text = "?"}); - rows.AddChild(new Label {Text = "°"}); - - rows.AddChild(new Label {Text = "Panel Angle:"}); - rows.AddChild(new Label {Text = ""}); - - rows.AddChild(PanelRotation = new LineEdit()); - rows.AddChild(new Label {Text = "°"}); - - rows.AddChild(new Label {Text = "Panel Angular Velocity:"}); - rows.AddChild(new Label {Text = ""}); - - rows.AddChild(PanelVelocity = new LineEdit()); - rows.AddChild(new Label {Text = "°/min."}); - - rows.AddChild(new Label {Text = "Press Enter to confirm."}); - rows.AddChild(new Label {Text = ""}); - - PanelRotation.HorizontalExpand = true; - PanelVelocity.HorizontalExpand = true; - - NotARadar = new SolarControlNotARadar(igt); - - var outerColumns = new HBoxContainer(); - outerColumns.AddChild(rows); - outerColumns.AddChild(NotARadar); - Contents.AddChild(outerColumns); - Resizable = false; - } - } - - private sealed class SolarControlNotARadar : Control - { - // IoC doesn't apply here, so it's propagated from the parent class. - // This is used for client-side prediction of the panel rotation. - // This makes the display feel a lot smoother. - private IGameTiming _gameTiming; - - private SolarControlConsoleBoundInterfaceState _lastState = new(0, 0, 0, 0); - - private TimeSpan _lastStateTime = TimeSpan.Zero; - - public const int SizeFull = 290; - public const int RadiusCircle = 140; - - public SolarControlNotARadar(IGameTiming igt) - { - _gameTiming = igt; - MinSize = (SizeFull, SizeFull); - } - - public void UpdateState(SolarControlConsoleBoundInterfaceState ls) - { - _lastState = ls; - _lastStateTime = _gameTiming.CurTime; - } - - protected override void Draw(DrawingHandleScreen handle) - { - var point = SizeFull / 2; - var fakeAA = new Color(0.08f, 0.08f, 0.08f); - var gridLines = new Color(0.08f, 0.08f, 0.08f); - var panelExtentCutback = 4; - var gridLinesRadial = 8; - var gridLinesEquatorial = 8; - - // Draw base - handle.DrawCircle((point, point), RadiusCircle + 1, fakeAA); - handle.DrawCircle((point, point), RadiusCircle, Color.Black); - - // Draw grid lines - for (var i = 0; i < gridLinesEquatorial; i++) - { - handle.DrawCircle((point, point), (RadiusCircle / gridLinesEquatorial) * i, gridLines, false); - } - - for (var i = 0; i < gridLinesRadial; i++) - { - Angle angle = (Math.PI / gridLinesRadial) * i; - var aExtent = angle.ToVec() * RadiusCircle; - handle.DrawLine((point, point) - aExtent, (point, point) + aExtent, gridLines); - } - - // The rotations need to be adjusted because Y is inverted in Robust (like BYOND) - Vector2 rotMul = (1, -1); - - Angle predictedPanelRotation = _lastState.Rotation + (_lastState.AngularVelocity * ((_gameTiming.CurTime - _lastStateTime).TotalSeconds)); - - var extent = predictedPanelRotation.ToVec() * rotMul * RadiusCircle; - Vector2 extentOrtho = (extent.Y, -extent.X); - handle.DrawLine((point, point) - extentOrtho, (point, point) + extentOrtho, Color.White); - handle.DrawLine((point, point) + (extent / panelExtentCutback), (point, point) + extent - (extent / panelExtentCutback), Color.DarkGray); - - var sunExtent = _lastState.TowardsSun.ToVec() * rotMul * RadiusCircle; - handle.DrawLine((point, point) + sunExtent, (point, point), Color.Yellow); - } - } - } -} diff --git a/Content.Server/GameObjects/Components/BaseComputerUserInterfaceComponent.cs b/Content.Server/GameObjects/Components/BaseComputerUserInterfaceComponent.cs new file mode 100644 index 0000000000..bb2b23e297 --- /dev/null +++ b/Content.Server/GameObjects/Components/BaseComputerUserInterfaceComponent.cs @@ -0,0 +1,141 @@ +using Content.Server.Power.Components; +using Content.Server.UserInterface; +using Content.Shared.ActionBlocker; +using Content.Shared.Notification; +using Content.Shared.Interaction; +using Content.Shared.GameObjects.Components; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Server.GameObjects; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.ViewVariables; +using Robust.Shared.Localization; + +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 PowerReceiverComponent? receiver) || receiver.Powered; + + public BaseComputerUserInterfaceComponent(object key) + { + UserInterfaceKey = key; + } + + public 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 (!ActionBlockerSystem.CanInteract(sessionEntity)) + { + 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! + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + 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/GameObjects/EntitySystems/ComputerUIActivatorSystem.cs b/Content.Server/GameObjects/EntitySystems/ComputerUIActivatorSystem.cs new file mode 100644 index 0000000000..f0689cfcbf --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/ComputerUIActivatorSystem.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using Content.Shared.Interaction; +using Content.Server.GameObjects.Components; +using Content.Shared.GameTicking; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using JetBrains.Annotations; + +namespace Content.Server.GameObjects.EntitySystems +{ + [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/Solar/Components/SolarControlConsoleComponent.cs b/Content.Server/Solar/Components/SolarControlConsoleComponent.cs index 48c9f6c2b4..080dc9fc48 100644 --- a/Content.Server/Solar/Components/SolarControlConsoleComponent.cs +++ b/Content.Server/Solar/Components/SolarControlConsoleComponent.cs @@ -1,9 +1,8 @@ -#nullable enable -using Content.Server.Power.Components; -using Content.Server.Solar.EntitySystems; -using Content.Server.UserInterface; -using Content.Shared.Interaction; +#nullable enable using Content.Shared.Solar; +using Content.Server.Solar.EntitySystems; +using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -12,26 +11,20 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Solar.Components { [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class SolarControlConsoleComponent : SharedSolarControlConsoleComponent, IActivate + [ComponentReference(typeof(BaseComputerUserInterfaceComponent))] + public class SolarControlConsoleComponent : BaseComputerUserInterfaceComponent { + public override string Name => "SolarControlConsole"; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; private PowerSolarSystem _powerSolarSystem = default!; - private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SolarControlConsoleUiKey.Key); + public SolarControlConsoleComponent() : base(SolarControlConsoleUiKey.Key) { } public override void Initialize() { base.Initialize(); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; - } - - Owner.EnsureComponent(); _powerSolarSystem = _entitySystemManager.GetEntitySystem(); } @@ -40,7 +33,7 @@ namespace Content.Server.Solar.Components UserInterface?.SetState(new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun)); } - private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage obj) + protected override void OnReceiveUserInterfaceMessage(ServerBoundUserInterfaceMessage obj) { switch (obj.Message) { @@ -56,22 +49,5 @@ namespace Content.Server.Solar.Components break; } } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - if (!Powered) - { - return; - } - - // always update the UI immediately before opening, just in case - UpdateUIState(); - UserInterface?.Open(actor.PlayerSession); - } } } diff --git a/Content.Server/Solar/EntitySystems/PowerSolarSystem.cs b/Content.Server/Solar/EntitySystems/PowerSolarSystem.cs index 19490c0928..32460e5cae 100644 --- a/Content.Server/Solar/EntitySystems/PowerSolarSystem.cs +++ b/Content.Server/Solar/EntitySystems/PowerSolarSystem.cs @@ -137,7 +137,7 @@ namespace Content.Server.Solar.EntitySystems { // Determine if the solar panel is occluded, and zero out coverage if so. // FIXME: The "Opaque" collision group doesn't seem to work right now. - var ray = new CollisionRay(entity.Transform.WorldPosition, TowardsSun.ToVec(), (int) CollisionGroup.Opaque); + var ray = new CollisionRay(entity.Transform.WorldPosition, TowardsSun.ToWorldVec(), (int) CollisionGroup.Opaque); var rayCastResults = EntitySystem.Get().IntersectRay(entity.Transform.MapID, ray, SunOcclusionCheckDistance, entity); if (rayCastResults.Any()) coverage = 0; diff --git a/Content.Shared/Solar/SharedSolarControlConsoleComponent.cs b/Content.Shared/Solar/SharedSolarControlConsoleComponent.cs index dd00fdb1f4..492e83f7f0 100644 --- a/Content.Shared/Solar/SharedSolarControlConsoleComponent.cs +++ b/Content.Shared/Solar/SharedSolarControlConsoleComponent.cs @@ -6,12 +6,6 @@ using Robust.Shared.Serialization; namespace Content.Shared.Solar { - public class SharedSolarControlConsoleComponent : Component - { - public override string Name => "SolarControlConsole"; - - } - [Serializable, NetSerializable] public class SolarControlConsoleBoundInterfaceState : BoundUserInterfaceState { diff --git a/Resources/Locale/en-US/components/base-computer-ui-component.ftl b/Resources/Locale/en-US/components/base-computer-ui-component.ftl new file mode 100644 index 0000000000..dce198278b --- /dev/null +++ b/Resources/Locale/en-US/components/base-computer-ui-component.ftl @@ -0,0 +1,3 @@ +base-computer-ui-component-cannot-interact = You can't interact with a computer right now. +base-computer-ui-component-not-powered = The computer is not powered. + diff --git a/Resources/Locale/en-US/ui/solar-control.ftl b/Resources/Locale/en-US/ui/solar-control.ftl new file mode 100644 index 0000000000..eb694df84a --- /dev/null +++ b/Resources/Locale/en-US/ui/solar-control.ftl @@ -0,0 +1,10 @@ +solar-control-window-title = Solar Control Console +solar-control-window-output-power = Output Power: +solar-control-window-watts = W +solar-control-window-sun-angle = Sun Angle: +solar-control-window-degrees = ° +solar-control-window-panel-angle = Panel Angle: +solar-control-window-panel-angular-velocity = Panel Angular Velocity: +solar-control-window-degrees-per-minute = °/min. +solar-control-window-press-enter-to-confirm = Press Enter to confirm. +