From e11a9b282a93b9256a0aed236c994a9ee8241d17 Mon Sep 17 00:00:00 2001 From: Paul Ritter Date: Fri, 27 Aug 2021 17:46:02 +0200 Subject: [PATCH] machine linking refactor to ecs (#4323) * started work * some more work, ui working (somewhat) * stuff * reorganization * some more reorg * conveyors * conveyors working * finalized (dis)connection added linkattempt added feedback text work on conveyors * removed command add rangecheck * fixed inrange check * handling * ui no longer kanser, ship it * adresses reviews * reformats file * reformats file Co-authored-by: Paul --- .../SignalPortSelectorBoundUserInterface.cs | 50 +++ .../UI/SignalPortSelectorMenu.xaml | 4 + .../UI/SignalPortSelectorMenu.xaml.cs | 36 +++ Content.Server/Conveyor/ConveyorComponent.cs | 9 +- Content.Server/Conveyor/ConveyorSystem.cs | 51 +++ .../Light/Components/PoweredLightComponent.cs | 13 +- .../Light/EntitySystems/PoweredLightSystem.cs | 26 ++ .../Components/ISignalReceiver.cs | 7 - .../Components/SignalButtonComponent.cs | 37 +-- .../Components/SignalLinkerComponent.cs | 9 +- .../Components/SignalReceiverComponent.cs | 120 +------ .../Components/SignalSwitchComponent.cs | 78 +---- .../Components/SignalTransmitterComponent.cs | 150 +-------- .../Components/SignalTwoWayLeverComponent.cs | 64 ---- .../Components/TwoWayLeverComponent.cs | 15 + .../MachineLinking/Events/InvokePortEvent.cs | 16 + .../MachineLinking/Events/LinkAttemptEvent.cs | 23 ++ .../Events/PortDisconnectedEvent.cs | 14 + .../Events/SignalReceivedEvent.cs | 16 + .../Events/SignalValueRequestedEvent.cs | 19 ++ .../Exceptions/InvalidPortValueException.cs | 9 + .../LinkAlreadyRegisteredException.cs | 9 + .../NoSignalValueProvidedException.cs | 9 + .../Exceptions/PortNotFoundException.cs | 9 + .../MachineLinking/Models/SignalLink.cs | 20 ++ .../Models/SignalLinkCollection.cs | 131 ++++++++ .../MachineLinking/Models/SignalPort.cs | 81 +++++ .../MachineLinking/SignalLinkerCommand.cs | 56 ---- .../MachineLinking/SignalLinkerSystem.cs | 92 ------ .../MachineLinking/Signals/ToggleSignal.cs | 4 - .../System/SignalButtonSystem.cs | 23 ++ .../System/SignalLinkerSystem.cs | 296 ++++++++++++++++++ .../System/SignalSwitchSystem.cs | 35 +++ .../System/TwoWayLeverSystem.cs | 54 ++++ .../MachineLinking/TwoWayLeverSignal.cs | 4 +- Content.Shared/MachineLinking/UIKeys.cs | 17 + Content.Shared/MachineLinking/UIMessages.cs | 37 +++ .../en-US/conveyors/conveyor-component.ftl | 1 + .../components/signal-button-component.ftl | 2 - .../components/signal-linker-component.ftl | 10 + .../components/signal-receiver-component.ftl | 5 - .../components/signal-switch-component.ftl | 4 - .../signal-transmitter-component.ftl | 1 - .../signal-two-way-lever-component.ftl | 1 - .../Structures/Wallmounts/lighting.yml | 6 + .../Entities/Structures/Wallmounts/switch.yml | 10 + .../Entities/Structures/conveyor.yml | 9 +- 47 files changed, 1059 insertions(+), 633 deletions(-) create mode 100644 Content.Client/MachineLinking/UI/SignalPortSelectorBoundUserInterface.cs create mode 100644 Content.Client/MachineLinking/UI/SignalPortSelectorMenu.xaml create mode 100644 Content.Client/MachineLinking/UI/SignalPortSelectorMenu.xaml.cs create mode 100644 Content.Server/Conveyor/ConveyorSystem.cs create mode 100644 Content.Server/Light/EntitySystems/PoweredLightSystem.cs delete mode 100644 Content.Server/MachineLinking/Components/ISignalReceiver.cs delete mode 100644 Content.Server/MachineLinking/Components/SignalTwoWayLeverComponent.cs create mode 100644 Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs create mode 100644 Content.Server/MachineLinking/Events/InvokePortEvent.cs create mode 100644 Content.Server/MachineLinking/Events/LinkAttemptEvent.cs create mode 100644 Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs create mode 100644 Content.Server/MachineLinking/Events/SignalReceivedEvent.cs create mode 100644 Content.Server/MachineLinking/Events/SignalValueRequestedEvent.cs create mode 100644 Content.Server/MachineLinking/Exceptions/InvalidPortValueException.cs create mode 100644 Content.Server/MachineLinking/Exceptions/LinkAlreadyRegisteredException.cs create mode 100644 Content.Server/MachineLinking/Exceptions/NoSignalValueProvidedException.cs create mode 100644 Content.Server/MachineLinking/Exceptions/PortNotFoundException.cs create mode 100644 Content.Server/MachineLinking/Models/SignalLink.cs create mode 100644 Content.Server/MachineLinking/Models/SignalLinkCollection.cs create mode 100644 Content.Server/MachineLinking/Models/SignalPort.cs delete mode 100644 Content.Server/MachineLinking/SignalLinkerCommand.cs delete mode 100644 Content.Server/MachineLinking/SignalLinkerSystem.cs delete mode 100644 Content.Server/MachineLinking/Signals/ToggleSignal.cs create mode 100644 Content.Server/MachineLinking/System/SignalButtonSystem.cs create mode 100644 Content.Server/MachineLinking/System/SignalLinkerSystem.cs create mode 100644 Content.Server/MachineLinking/System/SignalSwitchSystem.cs create mode 100644 Content.Server/MachineLinking/System/TwoWayLeverSystem.cs create mode 100644 Content.Shared/MachineLinking/UIKeys.cs create mode 100644 Content.Shared/MachineLinking/UIMessages.cs create mode 100644 Resources/Locale/en-US/conveyors/conveyor-component.ftl delete mode 100644 Resources/Locale/en-US/machine-linking/components/signal-button-component.ftl create mode 100644 Resources/Locale/en-US/machine-linking/components/signal-linker-component.ftl delete mode 100644 Resources/Locale/en-US/machine-linking/components/signal-receiver-component.ftl delete mode 100644 Resources/Locale/en-US/machine-linking/components/signal-switch-component.ftl delete mode 100644 Resources/Locale/en-US/machine-linking/components/signal-transmitter-component.ftl delete mode 100644 Resources/Locale/en-US/machine-linking/components/signal-two-way-lever-component.ftl diff --git a/Content.Client/MachineLinking/UI/SignalPortSelectorBoundUserInterface.cs b/Content.Client/MachineLinking/UI/SignalPortSelectorBoundUserInterface.cs new file mode 100644 index 0000000000..ec9bdcb88b --- /dev/null +++ b/Content.Client/MachineLinking/UI/SignalPortSelectorBoundUserInterface.cs @@ -0,0 +1,50 @@ +using Content.Shared.MachineLinking; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; + +namespace Content.Client.MachineLinking.UI +{ + public class SignalPortSelectorBoundUserInterface : BoundUserInterface + { + private SignalPortSelectorMenu? _menu; + + public SignalPortSelectorBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _menu = new SignalPortSelectorMenu(this); + _menu.OnClose += Close; + _menu.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + switch (state) + { + case SignalPortsState data: + _menu?.UpdateState(data); + break; + } + } + + public void OnPortSelected(string port) + { + SendMessage(new SignalPortSelected(port)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Dispose(); + } + } +} diff --git a/Content.Client/MachineLinking/UI/SignalPortSelectorMenu.xaml b/Content.Client/MachineLinking/UI/SignalPortSelectorMenu.xaml new file mode 100644 index 0000000000..cd78954b8c --- /dev/null +++ b/Content.Client/MachineLinking/UI/SignalPortSelectorMenu.xaml @@ -0,0 +1,4 @@ + + + diff --git a/Content.Client/MachineLinking/UI/SignalPortSelectorMenu.xaml.cs b/Content.Client/MachineLinking/UI/SignalPortSelectorMenu.xaml.cs new file mode 100644 index 0000000000..ce95e731a9 --- /dev/null +++ b/Content.Client/MachineLinking/UI/SignalPortSelectorMenu.xaml.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Content.Shared.MachineLinking; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.MachineLinking.UI +{ + [GenerateTypedNameReferences] + public partial class SignalPortSelectorMenu : SS14Window + { + private SignalPortSelectorBoundUserInterface _bui; + + public SignalPortSelectorMenu(SignalPortSelectorBoundUserInterface boundUserInterface) + { + RobustXamlLoader.Load(this); + _bui = boundUserInterface; + } + + public void UpdateState(SignalPortsState state) + { + ButtonContainer.Clear(); + foreach (var port in state.Ports) + { + var portBtn = new ItemList.Item(ButtonContainer) + { + Text = port.Key, + Disabled = !port.Value + }; + portBtn.OnSelected += _ => _bui.OnPortSelected(port.Key); + ButtonContainer.Add(portBtn); + } + } + } +} diff --git a/Content.Server/Conveyor/ConveyorComponent.cs b/Content.Server/Conveyor/ConveyorComponent.cs index 6109ea9cdc..175f9faa2f 100644 --- a/Content.Server/Conveyor/ConveyorComponent.cs +++ b/Content.Server/Conveyor/ConveyorComponent.cs @@ -14,7 +14,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Conveyor { [RegisterComponent] - public class ConveyorComponent : Component, ISignalReceiver, ISignalReceiver + public class ConveyorComponent : Component { public override string Name => "Conveyor"; @@ -150,7 +150,7 @@ namespace Content.Server.Conveyor return true; } - public void TriggerSignal(TwoWayLeverSignal signal) + public void SetState(TwoWayLeverSignal signal) { State = signal switch { @@ -160,10 +160,5 @@ namespace Content.Server.Conveyor _ => ConveyorState.Off }; } - - public void TriggerSignal(bool signal) - { - State = signal ? ConveyorState.Forward : ConveyorState.Off; - } } } diff --git a/Content.Server/Conveyor/ConveyorSystem.cs b/Content.Server/Conveyor/ConveyorSystem.cs new file mode 100644 index 0000000000..c416b77f5d --- /dev/null +++ b/Content.Server/Conveyor/ConveyorSystem.cs @@ -0,0 +1,51 @@ +using Content.Server.MachineLinking.Events; +using Content.Server.MachineLinking.Models; +using Content.Server.Stunnable.Components; +using Content.Shared.MachineLinking; +using Content.Shared.Notification.Managers; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Conveyor +{ + public class ConveyorSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSignalReceived); + SubscribeLocalEvent(OnPortDisconnected); + SubscribeLocalEvent(OnLinkAttempt); + } + + private void OnLinkAttempt(EntityUid uid, ConveyorComponent component, LinkAttemptEvent args) + { + if (args.TransmitterComponent.Outputs.GetPort(args.TransmitterPort).Signal is TwoWayLeverSignal signal && + signal != TwoWayLeverSignal.Middle) + { + args.Cancel(); + if (args.Attemptee.TryGetComponent(out var stunnableComponent)) + { + stunnableComponent.Paralyze(2); + component.Owner.PopupMessage(args.Attemptee, Loc.GetString("conveyor-component-failed-link")); + } + } + } + + private void OnPortDisconnected(EntityUid uid, ConveyorComponent component, PortDisconnectedEvent args) + { + component.SetState(TwoWayLeverSignal.Middle); + } + + private void OnSignalReceived(EntityUid uid, ConveyorComponent component, SignalReceivedEvent args) + { + switch (args.Port) + { + case "state": + component.SetState((TwoWayLeverSignal) args.Value!); + break; + } + } + } +} diff --git a/Content.Server/Light/Components/PoweredLightComponent.cs b/Content.Server/Light/Components/PoweredLightComponent.cs index f666e251ec..a942b6fcd9 100644 --- a/Content.Server/Light/Components/PoweredLightComponent.cs +++ b/Content.Server/Light/Components/PoweredLightComponent.cs @@ -3,8 +3,6 @@ using System.Threading.Tasks; using Content.Server.Ghost; using Content.Server.Hands.Components; using Content.Server.Items; -using Content.Server.MachineLinking.Components; -using Content.Server.MachineLinking.Signals; using Content.Server.Power.Components; using Content.Server.Temperature.Components; using Content.Shared.Actions.Behaviors; @@ -12,7 +10,6 @@ using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Interaction; using Content.Shared.Light; -using Content.Shared.Notification; using Content.Shared.Notification.Managers; using Content.Shared.Sound; using Robust.Server.GameObjects; @@ -33,7 +30,7 @@ namespace Content.Server.Light.Components /// Component that represents a wall light. It has a light bulb that can be replaced when broken. /// [RegisterComponent] - public class PoweredLightComponent : Component, IInteractHand, IInteractUsing, IMapInit, ISignalReceiver, ISignalReceiver, IGhostBooAffected + public class PoweredLightComponent : Component, IInteractHand, IInteractUsing, IMapInit, IGhostBooAffected { [Dependency] private readonly IGameTiming _gameTiming = default!; @@ -309,13 +306,7 @@ namespace Content.Server.Light.Components UpdateLight(); } - public void TriggerSignal(bool signal) - { - _on = signal; - UpdateLight(); - } - - public void TriggerSignal(ToggleSignal signal) + public void ToggleLight() { _on = !_on; UpdateLight(); diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs new file mode 100644 index 0000000000..9318882b93 --- /dev/null +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -0,0 +1,26 @@ +using Content.Server.Light.Components; +using Content.Server.MachineLinking.Events; +using Robust.Shared.GameObjects; + +namespace Content.Server.Light.EntitySystems +{ + public class PoweredLightSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSignalReceived); + } + + private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, SignalReceivedEvent args) + { + switch (args.Port) + { + case "toggle": + component.ToggleLight(); + break; + } + } + } +} diff --git a/Content.Server/MachineLinking/Components/ISignalReceiver.cs b/Content.Server/MachineLinking/Components/ISignalReceiver.cs deleted file mode 100644 index 1b36d893a4..0000000000 --- a/Content.Server/MachineLinking/Components/ISignalReceiver.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Server.MachineLinking.Components -{ - public interface ISignalReceiver - { - void TriggerSignal(T signal); - } -} diff --git a/Content.Server/MachineLinking/Components/SignalButtonComponent.cs b/Content.Server/MachineLinking/Components/SignalButtonComponent.cs index eecfd3970e..2bacd34fca 100644 --- a/Content.Server/MachineLinking/Components/SignalButtonComponent.cs +++ b/Content.Server/MachineLinking/Components/SignalButtonComponent.cs @@ -1,45 +1,10 @@ -using Content.Server.MachineLinking.Signals; -using Content.Shared.Interaction; -using Content.Shared.Notification; -using Content.Shared.Notification.Managers; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; namespace Content.Server.MachineLinking.Components { [RegisterComponent] - public class SignalButtonComponent : Component, IActivate, IInteractHand + public class SignalButtonComponent : Component { public override string Name => "SignalButton"; - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - TransmitSignal(eventArgs.User); - } - - bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) - { - TransmitSignal(eventArgs.User); - return true; - } - - private void TransmitSignal(IEntity user) - { - if (!Owner.TryGetComponent(out var transmitter)) - { - return; - } - - if (transmitter.TransmitSignal(new ToggleSignal())) - { - // Since the button doesn't have an animation, I'm going to use a popup message - Owner.PopupMessage(user, Loc.GetString("signal-button-component-transmit-signal-success-message")); - } - else - { - Owner.PopupMessage(user, Loc.GetString("signal-button-component-transmit-signal-no-receivers-message")); - } - } - } } diff --git a/Content.Server/MachineLinking/Components/SignalLinkerComponent.cs b/Content.Server/MachineLinking/Components/SignalLinkerComponent.cs index f0b99220ad..bf9d430c44 100644 --- a/Content.Server/MachineLinking/Components/SignalLinkerComponent.cs +++ b/Content.Server/MachineLinking/Components/SignalLinkerComponent.cs @@ -9,13 +9,6 @@ namespace Content.Server.MachineLinking.Components public override string Name => "SignalLinker"; [ViewVariables] - public SignalTransmitterComponent? Link { get; set; } - - protected override void Initialize() - { - base.Initialize(); - - Link = null; - } + public (SignalTransmitterComponent transmitter, string port)? Port; } } diff --git a/Content.Server/MachineLinking/Components/SignalReceiverComponent.cs b/Content.Server/MachineLinking/Components/SignalReceiverComponent.cs index eef43e4916..2c82bc7300 100644 --- a/Content.Server/MachineLinking/Components/SignalReceiverComponent.cs +++ b/Content.Server/MachineLinking/Components/SignalReceiverComponent.cs @@ -1,125 +1,21 @@ +using System; using System.Collections.Generic; -using System.Threading.Tasks; -using Content.Server.Tools.Components; -using Content.Shared.Interaction; -using Content.Shared.Notification; -using Content.Shared.Notification.Managers; -using Content.Shared.Tool; +using Content.Server.MachineLinking.Models; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; namespace Content.Server.MachineLinking.Components { [RegisterComponent] - public class SignalReceiverComponent : Component, IInteractUsing + public class SignalReceiverComponent : Component { public override string Name => "SignalReceiver"; - private readonly List _transmitters = new(); + [DataField("inputs")] + private List _inputs = new(); - [DataField("maxTransmitters")] - private int? _maxTransmitters = default; - - public void DistributeSignal(T state) - { - foreach (var comp in Owner.GetAllComponents>()) - { - comp.TriggerSignal(state); - } - } - - public bool Subscribe(SignalTransmitterComponent transmitter) - { - if (_transmitters.Contains(transmitter)) - { - return true; - } - - if (_transmitters.Count >= _maxTransmitters) return false; - - transmitter.Subscribe(this); - _transmitters.Add(transmitter); - return true; - } - - public void Unsubscribe(SignalTransmitterComponent transmitter) - { - transmitter.Unsubscribe(this); - _transmitters.Remove(transmitter); - } - - public void UnsubscribeAll() - { - for (var i = _transmitters.Count-1; i >= 0; i--) - { - var transmitter = _transmitters[i]; - if (transmitter.Deleted) - { - continue; - } - - transmitter.Unsubscribe(this); - } - } - - /// - /// Subscribes/Unsubscribes a transmitter to this component. Returns whether it was successful. - /// - /// - /// - /// - public bool Interact(IEntity user, SignalTransmitterComponent? transmitter) - { - if (transmitter == null) - { - user.PopupMessage(Loc.GetString("signal-receiver-component-interact-no-transmitter-message")); - return false; - } - - if (_transmitters.Contains(transmitter)) - { - Unsubscribe(transmitter); - Owner.PopupMessage(user, Loc.GetString("signal-receiver-component-interact-unlinked")); - return true; - } - - if (transmitter.Range > 0 && !Owner.Transform.Coordinates.InRange(Owner.EntityManager, transmitter.Owner.Transform.Coordinates, transmitter.Range)) - { - Owner.PopupMessage(user, Loc.GetString("signal-receiver-component-interact-out-of-range")); - return false; - } - - if (!Subscribe(transmitter)) - { - Owner.PopupMessage(user, Loc.GetString("signal-receiver-component-interact-max-transmitters-limit")); - return false; - } - Owner.PopupMessage(user, Loc.GetString("signal-receiver-component-interact-success")); - return true; - } - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - if (!eventArgs.Using.TryGetComponent(out var tool)) - return false; - - if (tool.HasQuality(ToolQuality.Multitool) - && eventArgs.Using.TryGetComponent(out var linker)) - { - return Interact(eventArgs.User, linker.Link); - } - - return false; - } - - protected override void Shutdown() - { - base.Shutdown(); - - UnsubscribeAll(); - - _transmitters.Clear(); - } + [ViewVariables] + public IReadOnlyList Inputs => _inputs; } } diff --git a/Content.Server/MachineLinking/Components/SignalSwitchComponent.cs b/Content.Server/MachineLinking/Components/SignalSwitchComponent.cs index f4b5d79daa..eb8a4f37a8 100644 --- a/Content.Server/MachineLinking/Components/SignalSwitchComponent.cs +++ b/Content.Server/MachineLinking/Components/SignalSwitchComponent.cs @@ -1,86 +1,12 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.MachineLinking; -using Content.Shared.Notification.Managers; -using Content.Shared.Verbs; -using Robust.Server.GameObjects; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.MachineLinking.Components { [RegisterComponent] - public class SignalSwitchComponent : Component, IInteractHand, IActivate + public class SignalSwitchComponent : Component { public override string Name => "SignalSwitch"; - [DataField("on")] - private bool _on; - - protected override void Initialize() - { - base.Initialize(); - - UpdateSprite(); - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - TransmitSignal(eventArgs.User); - } - - bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) - { - TransmitSignal(eventArgs.User); - return true; - } - - public void TransmitSignal(IEntity user) - { - _on = !_on; - - UpdateSprite(); - - if (!Owner.TryGetComponent(out var transmitter)) - { - return; - } - - if (!transmitter.TransmitSignal(_on)) - { - Owner.PopupMessage(user, Loc.GetString("signal-switch-component-transmit-no-receivers-connected")); - } - } - - private void UpdateSprite() - { - if (Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - appearance.SetData(SignalSwitchVisuals.On, _on); - } - } - - [Verb] - private sealed class ToggleSwitchVerb : Verb - { - protected override void Activate(IEntity user, SignalSwitchComponent component) - { - component.TransmitSignal(user); - } - - protected override void GetData(IEntity user, SignalSwitchComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user)) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString("toggle-switch-verb-get-data-text"); - data.Visibility = VerbVisibility.Visible; - } - } + public bool State; } } diff --git a/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs b/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs index 94d2f95509..9e4982f832 100644 --- a/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs +++ b/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs @@ -1,159 +1,21 @@ +using System; using System.Collections.Generic; -using System.Threading.Tasks; -using Content.Server.Tools.Components; -using Content.Shared.Interaction; -using Content.Shared.Notification; -using Content.Shared.Notification.Managers; -using Content.Shared.Tool; +using Content.Server.MachineLinking.Models; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; namespace Content.Server.MachineLinking.Components { [RegisterComponent] - public class SignalTransmitterComponent : Component, IInteractUsing, ISerializationHooks + public class SignalTransmitterComponent : Component { public override string Name => "SignalTransmitter"; - private List? _unresolvedReceivers = new(); - private List _receivers = new(); + [DataField("outputs")] + private List _outputs = new(); - /// - /// 0 is unlimited range - /// [ViewVariables] - [DataField("range")] - public float Range { get; private set; } = 10; - - [DataField("signalReceivers")] private List _receiverIds = new(); - - void ISerializationHooks.BeforeSerialization() - { - var entityList = new List(); - - foreach (var receiver in _receivers) - { - if (receiver.Deleted) - { - continue; - } - - entityList.Add(receiver.Owner.Uid); - } - - _receiverIds = entityList; - } - - void ISerializationHooks.AfterDeserialization() - { - _unresolvedReceivers = new List(); - - foreach (var id in _receiverIds) - { - if (!Owner.EntityManager.TryGetEntity(id, out var entity) || - !entity.TryGetComponent(out var receiver)) - { - continue; - } - - _unresolvedReceivers.Add(receiver); - } - } - - protected override void Initialize() - { - base.Initialize(); - - _receivers = new List(); - - if (_unresolvedReceivers != null) - { - foreach (var receiver in _unresolvedReceivers) - { - receiver.Subscribe(this); - } - - _unresolvedReceivers = null; - } - } - - public bool TransmitSignal(T signal) - { - if (_receivers.Count == 0) - { - return false; - } - - foreach (var receiver in _receivers) - { - if (Range > 0 && !Owner.Transform.Coordinates.InRange(Owner.EntityManager, receiver.Owner.Transform.Coordinates, Range)) - { - continue; - } - - receiver.DistributeSignal(signal); - } - return true; - } - - public void Subscribe(SignalReceiverComponent receiver) - { - if (_receivers.Contains(receiver)) - { - return; - } - - _receivers.Add(receiver); - } - - public void Unsubscribe(SignalReceiverComponent receiver) - { - _receivers.Remove(receiver); - } - - public SignalTransmitterComponent GetSignal(IEntity? user) - { - if (user != null) - { - Owner.PopupMessage(user, Loc.GetString("signal-transmitter-component-get-signal-success")); - } - - return this; - } - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - if (!eventArgs.Using.TryGetComponent(out var tool)) - return false; - - if (tool.HasQuality(ToolQuality.Multitool) - && eventArgs.Using.TryGetComponent(out var linker)) - { - linker.Link = GetSignal(eventArgs.User); - } - - return false; - } - - protected override void Shutdown() - { - base.Shutdown(); - - for (var i = _receivers.Count-1; i >= 0; i++) - { - var receiver = _receivers[i]; - if (receiver.Deleted) - { - continue; - } - - receiver.Unsubscribe(this); - } - - _receivers.Clear(); - } + public IReadOnlyList Outputs => _outputs; } } diff --git a/Content.Server/MachineLinking/Components/SignalTwoWayLeverComponent.cs b/Content.Server/MachineLinking/Components/SignalTwoWayLeverComponent.cs deleted file mode 100644 index 2ddad2d982..0000000000 --- a/Content.Server/MachineLinking/Components/SignalTwoWayLeverComponent.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Content.Shared.Interaction; -using Content.Shared.MachineLinking; -using Content.Shared.Notification; -using Content.Shared.Notification.Managers; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; - -namespace Content.Server.MachineLinking.Components -{ - [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class SignalTwoWayLeverComponent : SignalTransmitterComponent, IInteractHand, IActivate - { - public override string Name => "TwoWayLever"; - - private TwoWayLeverSignal _state = TwoWayLeverSignal.Middle; - - private bool _nextForward = true; - - public TwoWayLeverSignal State - { - get => _state; - private set - { - _state = value; - - if (Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - appearance.SetData(TwoWayLeverVisuals.State, value); - } - } - } - - private void NextState(IEntity user) - { - State = State switch - { - TwoWayLeverSignal.Left => TwoWayLeverSignal.Middle, - TwoWayLeverSignal.Middle => _nextForward ? TwoWayLeverSignal.Right : TwoWayLeverSignal.Left, - TwoWayLeverSignal.Right => TwoWayLeverSignal.Middle, - _ => TwoWayLeverSignal.Middle - }; - - if (State == TwoWayLeverSignal.Left || State == TwoWayLeverSignal.Right) _nextForward = !_nextForward; - - if (!TransmitSignal(State)) - { - Owner.PopupMessage(user, Loc.GetString("signal-two-way-lever-component-next-state-no-receivers-connected")); - } - } - - bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) - { - NextState(eventArgs.User); - return true; - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - NextState(eventArgs.User); - } - } -} diff --git a/Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs b/Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs new file mode 100644 index 0000000000..a0782a4567 --- /dev/null +++ b/Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.MachineLinking; +using Robust.Shared.GameObjects; + +namespace Content.Server.MachineLinking.Components +{ + [RegisterComponent] + public class TwoWayLeverComponent : Component + { + public override string Name => "TwoWayLever"; + + public TwoWayLeverSignal State; + + public bool NextSignalLeft; + } +} diff --git a/Content.Server/MachineLinking/Events/InvokePortEvent.cs b/Content.Server/MachineLinking/Events/InvokePortEvent.cs new file mode 100644 index 0000000000..31601ea9ff --- /dev/null +++ b/Content.Server/MachineLinking/Events/InvokePortEvent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.MachineLinking.Events +{ + public class InvokePortEvent : EntityEventArgs + { + public readonly string Port; + public readonly object? Value; + + public InvokePortEvent(string port, object? value = null) + { + Port = port; + Value = value; + } + } +} diff --git a/Content.Server/MachineLinking/Events/LinkAttemptEvent.cs b/Content.Server/MachineLinking/Events/LinkAttemptEvent.cs new file mode 100644 index 0000000000..0bde20c91e --- /dev/null +++ b/Content.Server/MachineLinking/Events/LinkAttemptEvent.cs @@ -0,0 +1,23 @@ +using Content.Server.MachineLinking.Components; +using Robust.Shared.GameObjects; + +namespace Content.Server.MachineLinking.Events +{ + public class LinkAttemptEvent : CancellableEntityEventArgs + { + public readonly IEntity Attemptee; + public readonly SignalTransmitterComponent TransmitterComponent; + public readonly string TransmitterPort; + public readonly SignalReceiverComponent ReceiverComponent; + public readonly string ReceiverPort; + + public LinkAttemptEvent(IEntity attemptee, SignalTransmitterComponent transmitterComponent, string transmitterPort, SignalReceiverComponent receiverComponent, string receiverPort) + { + TransmitterComponent = transmitterComponent; + this.TransmitterPort = transmitterPort; + ReceiverComponent = receiverComponent; + this.ReceiverPort = receiverPort; + Attemptee = attemptee; + } + } +} diff --git a/Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs b/Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs new file mode 100644 index 0000000000..c9b049d29d --- /dev/null +++ b/Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.MachineLinking.Events +{ + public class PortDisconnectedEvent : EntityEventArgs + { + public readonly string Port; + + public PortDisconnectedEvent(string port) + { + Port = port; + } + } +} diff --git a/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs b/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs new file mode 100644 index 0000000000..366db66c38 --- /dev/null +++ b/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.MachineLinking.Events +{ + public class SignalReceivedEvent : EntityEventArgs + { + public readonly string Port; + public readonly object? Value; + + public SignalReceivedEvent(string port, object? value) + { + Port = port; + Value = value; + } + } +} diff --git a/Content.Server/MachineLinking/Events/SignalValueRequestedEvent.cs b/Content.Server/MachineLinking/Events/SignalValueRequestedEvent.cs new file mode 100644 index 0000000000..4f84410505 --- /dev/null +++ b/Content.Server/MachineLinking/Events/SignalValueRequestedEvent.cs @@ -0,0 +1,19 @@ +using System; +using Robust.Shared.GameObjects; + +namespace Content.Server.MachineLinking.Events +{ + public class SignalValueRequestedEvent : HandledEntityEventArgs + { + public readonly string Port; + public readonly Type Type; + + public object? Signal; + + public SignalValueRequestedEvent(string port, Type type) + { + Port = port; + Type = type; + } + } +} diff --git a/Content.Server/MachineLinking/Exceptions/InvalidPortValueException.cs b/Content.Server/MachineLinking/Exceptions/InvalidPortValueException.cs new file mode 100644 index 0000000000..6e43b38c44 --- /dev/null +++ b/Content.Server/MachineLinking/Exceptions/InvalidPortValueException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Content.Server.MachineLinking.Exceptions +{ + public class InvalidPortValueException : Exception + { + + } +} diff --git a/Content.Server/MachineLinking/Exceptions/LinkAlreadyRegisteredException.cs b/Content.Server/MachineLinking/Exceptions/LinkAlreadyRegisteredException.cs new file mode 100644 index 0000000000..93398f3940 --- /dev/null +++ b/Content.Server/MachineLinking/Exceptions/LinkAlreadyRegisteredException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Content.Server.MachineLinking.Exceptions +{ + public class LinkAlreadyRegisteredException : Exception + { + + } +} diff --git a/Content.Server/MachineLinking/Exceptions/NoSignalValueProvidedException.cs b/Content.Server/MachineLinking/Exceptions/NoSignalValueProvidedException.cs new file mode 100644 index 0000000000..f2989bd1ce --- /dev/null +++ b/Content.Server/MachineLinking/Exceptions/NoSignalValueProvidedException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Content.Server.MachineLinking.Exceptions +{ + public class NoSignalValueProvidedException : Exception + { + + } +} diff --git a/Content.Server/MachineLinking/Exceptions/PortNotFoundException.cs b/Content.Server/MachineLinking/Exceptions/PortNotFoundException.cs new file mode 100644 index 0000000000..18b5279f8f --- /dev/null +++ b/Content.Server/MachineLinking/Exceptions/PortNotFoundException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Content.Server.MachineLinking.Exceptions +{ + public class PortNotFoundException : Exception + { + + } +} diff --git a/Content.Server/MachineLinking/Models/SignalLink.cs b/Content.Server/MachineLinking/Models/SignalLink.cs new file mode 100644 index 0000000000..9b14a3dd96 --- /dev/null +++ b/Content.Server/MachineLinking/Models/SignalLink.cs @@ -0,0 +1,20 @@ +using Content.Server.MachineLinking.Components; + +namespace Content.Server.MachineLinking.Models +{ + public class SignalLink + { + public readonly SignalTransmitterComponent TransmitterComponent; + public readonly SignalReceiverComponent ReceiverComponent; + public readonly SignalPort Transmitterport; + public readonly SignalPort Receiverport; + + public SignalLink(SignalTransmitterComponent transmitterComponent, string transmitterPort, SignalReceiverComponent receiverComponent, string receiverPort) + { + TransmitterComponent = transmitterComponent; + ReceiverComponent = receiverComponent; + Transmitterport = TransmitterComponent.Outputs.GetPort(transmitterPort); + Receiverport = ReceiverComponent.Inputs.GetPort(receiverPort); + } + } +} diff --git a/Content.Server/MachineLinking/Models/SignalLinkCollection.cs b/Content.Server/MachineLinking/Models/SignalLinkCollection.cs new file mode 100644 index 0000000000..7afd01d2a2 --- /dev/null +++ b/Content.Server/MachineLinking/Models/SignalLinkCollection.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using Content.Server.MachineLinking.Components; +using Content.Server.MachineLinking.Exceptions; + +namespace Content.Server.MachineLinking.Models +{ + public class SignalLinkCollection + { + private Dictionary> _transmitterDict = new(); + private Dictionary> _receiverDict = new(); + + public SignalLink AddLink(SignalTransmitterComponent transmitterComponent, string transmitterPort, + SignalReceiverComponent receiverComponent, string receiverPort) + { + if (LinkExists(transmitterComponent, transmitterPort, receiverComponent, receiverPort)) + { + throw new LinkAlreadyRegisteredException(); + } + + if (!_transmitterDict.ContainsKey(transmitterComponent)) + { + _transmitterDict[transmitterComponent] = new(); + } + + if (!_receiverDict.ContainsKey(receiverComponent)) + { + _receiverDict[receiverComponent] = new(); + } + + var link = new SignalLink(transmitterComponent, transmitterPort, receiverComponent, receiverPort); + _transmitterDict[transmitterComponent].Add(link); + _receiverDict[receiverComponent].Add(link); + + return link; + } + + public bool LinkExists(SignalTransmitterComponent transmitterComponent, string transmitterPort, + SignalReceiverComponent receiverComponent, string receiverPort) + { + if (!_transmitterDict.ContainsKey(transmitterComponent) || !_receiverDict.ContainsKey(receiverComponent)) + { + return false; + } + + foreach (var link in _transmitterDict[transmitterComponent]) + { + if (link.Transmitterport.Name == transmitterPort && link.Receiverport.Name == receiverPort && + link.ReceiverComponent == receiverComponent) + return true; + } + + return false; + } + + public bool RemoveLink(SignalTransmitterComponent transmitterComponent, string transmitterPort, + SignalReceiverComponent receiverComponent, string receiverPort) + { + if (!_transmitterDict.ContainsKey(transmitterComponent) || !_receiverDict.ContainsKey(receiverComponent)) + { + return false; + } + + SignalLink? theLink = null; + foreach (var link in _transmitterDict[transmitterComponent]) + { + if (link.Transmitterport.Name == transmitterPort && link.Receiverport.Name == receiverPort && + link.ReceiverComponent == receiverComponent) + { + theLink = link; + break; + } + } + + if (theLink == null) return false; + + _transmitterDict[transmitterComponent].Remove(theLink); + if (_transmitterDict[transmitterComponent].Count == 0) _transmitterDict.Remove(transmitterComponent); + _receiverDict[receiverComponent].Remove(theLink); + if (_receiverDict[receiverComponent].Count == 0) _receiverDict.Remove(receiverComponent); + return true; + } + + public int LinkCount(SignalTransmitterComponent comp) => + _transmitterDict.ContainsKey(comp) ? _transmitterDict[comp].Count : 0; + + public int LinkCount(SignalReceiverComponent comp) => + _receiverDict.ContainsKey(comp) ? _receiverDict[comp].Count : 0; + + public void RemoveLinks(SignalTransmitterComponent component) + { + if (!_transmitterDict.ContainsKey(component)) + { + return; + } + + foreach (var link in _transmitterDict[component]) + { + _receiverDict[link.ReceiverComponent].Remove(link); + if (_receiverDict[link.ReceiverComponent].Count == 0) _receiverDict.Remove(link.ReceiverComponent); + } + + _transmitterDict.Remove(component); + } + + public void RemoveLinks(SignalReceiverComponent component) + { + if (!_receiverDict.ContainsKey(component)) + { + return; + } + + foreach (var link in _receiverDict[component]) + { + _transmitterDict[link.TransmitterComponent].Remove(link); + if (_transmitterDict[link.TransmitterComponent].Count == 0) + _transmitterDict.Remove(link.TransmitterComponent); + } + } + + public IEnumerable GetLinks(SignalTransmitterComponent component, string port) + { + if (!_transmitterDict.ContainsKey(component)) yield break; + + foreach (var link in _transmitterDict[component]) + { + if (link.Transmitterport.Name != port) continue; + yield return link; + } + } + } +} diff --git a/Content.Server/MachineLinking/Models/SignalPort.cs b/Content.Server/MachineLinking/Models/SignalPort.cs new file mode 100644 index 0000000000..3dd36cceec --- /dev/null +++ b/Content.Server/MachineLinking/Models/SignalPort.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Content.Server.MachineLinking.Events; +using Content.Server.MachineLinking.Exceptions; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.MachineLinking.Models +{ + [DataDefinition] + public class SignalPort + { + [DataField("name", required: true)] public string Name { get; } = default!; + [DataField("type")] public Type? Type { get; } + /// + /// Maximum connections of the port. 0 means infinite. + /// + [DataField("maxConnections")] public int MaxConnections { get; } = 0; + + public object? Signal; + } + + public static class PortPrototypeExtensions{ + public static bool ContainsPort(this IReadOnlyList ports, string port) + { + foreach (var portPrototype in ports) + { + if (portPrototype.Name == port) + { + return true; + } + } + + return false; + } + + public static IEnumerable GetPortStrings(this IReadOnlyList ports) + { + foreach (var portPrototype in ports) + { + yield return portPrototype.Name; + } + } + + public static IEnumerable> GetValidatedPorts(this IReadOnlyList ports, Type? validType) + { + foreach (var portPrototype in ports) + { + yield return new KeyValuePair(portPrototype.Name, portPrototype.Type == validType); + } + } + + public static bool TryGetPort(this IReadOnlyList ports, string name, [NotNullWhen(true)] out SignalPort? port) + { + foreach (var portPrototype in ports) + { + if (portPrototype.Name == name) + { + port = portPrototype; + return true; + } + } + + port = null; + return false; + } + + public static SignalPort GetPort(this IReadOnlyList ports, string name) + { + foreach (var portPrototype in ports) + { + if (portPrototype.Name == name) + { + return portPrototype; + } + } + + throw new PortNotFoundException(); + } + } +} diff --git a/Content.Server/MachineLinking/SignalLinkerCommand.cs b/Content.Server/MachineLinking/SignalLinkerCommand.cs deleted file mode 100644 index 017eb11c96..0000000000 --- a/Content.Server/MachineLinking/SignalLinkerCommand.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Content.Server.Administration; -using Content.Shared.Administration; -using Robust.Server.Player; -using Robust.Shared.Console; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Server.MachineLinking -{ - [AdminCommand(AdminFlags.Debug)] - public class SignalLinkerCommand : IConsoleCommand - { - public string Command => "signallink"; - - public string Description => "Turns on signal linker mode. Click a transmitter to tune that signal and then click on each receiver to tune them to the transmitter signal."; - - public string Help => "signallink (on/off)"; - - public void Execute(IConsoleShell shell, string argStr, string[] args) - { - var player = (IPlayerSession?) shell.Player; - - if (player == null) - { - shell.WriteError("This command cannot be run locally."); - return; - } - - bool? enable = null; - if (args.Length > 0) - { - if (args[0] == "on") - enable = true; - else if (args[0] == "off") - enable = false; - else if (bool.TryParse(args[0], out var boolean)) - enable = boolean; - else if (int.TryParse(args[0], out var num)) - { - if (num == 1) - enable = true; - else if (num == 0) - enable = false; - } - } - - if (!IoCManager.Resolve().TryGetEntitySystem(out var system)) - { - return; - } - - var ret = system.SignalLinkerKeybind(player.UserId, enable); - shell.WriteLine(ret ? "Enabled" : "Disabled"); - } - } -} diff --git a/Content.Server/MachineLinking/SignalLinkerSystem.cs b/Content.Server/MachineLinking/SignalLinkerSystem.cs deleted file mode 100644 index 2526bc9d36..0000000000 --- a/Content.Server/MachineLinking/SignalLinkerSystem.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Collections.Generic; -using Content.Server.Interaction; -using Content.Server.MachineLinking.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.Input; -using Robust.Shared.Input.Binding; -using Robust.Shared.Map; -using Robust.Shared.Network; -using Robust.Shared.Players; - -namespace Content.Server.MachineLinking -{ - public class SignalLinkerSystem : EntitySystem - { - private readonly Dictionary _transmitters = new(); - - public bool SignalLinkerKeybind(NetUserId id, bool? enable) - { - enable ??= !_transmitters.ContainsKey(id); - - if (enable.Value) - { - if (_transmitters.ContainsKey(id)) - { - return true; - } - - if (_transmitters.Count == 0) - { - CommandBinds.Builder - .BindBefore(EngineKeyFunctions.Use, - new PointerInputCmdHandler(HandleUse), - typeof(InteractionSystem)) - .Register(); - } - - _transmitters.Add(id, null); - - } - else - { - if (!_transmitters.ContainsKey(id)) - { - return false; - } - - _transmitters.Remove(id); - if (_transmitters.Count == 0) - { - CommandBinds.Unregister(); - } - } - - return enable.Value; - } - - private bool HandleUse(ICommonSession? session, EntityCoordinates coords, EntityUid uid) - { - if (session?.AttachedEntity == null) - { - return false; - } - - if (!_transmitters.TryGetValue(session.UserId, out var signalTransmitter)) - { - return false; - } - - if (!EntityManager.TryGetEntity(uid, out var entity)) - { - return false; - } - - if (entity.TryGetComponent(out var signalReceiver)) - { - if (signalReceiver.Interact(session.AttachedEntity, signalTransmitter)) - { - return true; - } - } - - if (entity.TryGetComponent(out var transmitter)) - { - _transmitters[session.UserId] = transmitter.GetSignal(session.AttachedEntity); - - return true; - } - - return false; - } - } -} diff --git a/Content.Server/MachineLinking/Signals/ToggleSignal.cs b/Content.Server/MachineLinking/Signals/ToggleSignal.cs deleted file mode 100644 index 9c4ab77a89..0000000000 --- a/Content.Server/MachineLinking/Signals/ToggleSignal.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Content.Server.MachineLinking.Signals -{ - public struct ToggleSignal {} -} diff --git a/Content.Server/MachineLinking/System/SignalButtonSystem.cs b/Content.Server/MachineLinking/System/SignalButtonSystem.cs new file mode 100644 index 0000000000..a2799263e1 --- /dev/null +++ b/Content.Server/MachineLinking/System/SignalButtonSystem.cs @@ -0,0 +1,23 @@ +using Content.Server.MachineLinking.Components; +using Content.Server.MachineLinking.Events; +using Content.Shared.Interaction; +using Robust.Shared.GameObjects; + +namespace Content.Server.MachineLinking.System +{ + public class SignalButtonSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractHand); + } + + private void OnInteractHand(EntityUid uid, SignalButtonComponent component, InteractHandEvent args) + { + RaiseLocalEvent(uid, new InvokePortEvent("pressed"), false); + args.Handled = true; + } + } +} diff --git a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs new file mode 100644 index 0000000000..98127a7efd --- /dev/null +++ b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs @@ -0,0 +1,296 @@ +using System.Collections.Generic; +using System.Linq; +using Content.Server.Hands.Components; +using Content.Server.Interaction; +using Content.Server.MachineLinking.Components; +using Content.Server.MachineLinking.Events; +using Content.Server.MachineLinking.Exceptions; +using Content.Server.MachineLinking.Models; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.UserInterface; +using Content.Shared.Interaction; +using Content.Shared.MachineLinking; +using Content.Shared.Notification.Managers; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Utility; + +namespace Content.Server.MachineLinking.System +{ + public class SignalLinkerSystem : EntitySystem + { + [Dependency] private IComponentManager _componentManager = default!; + private InteractionSystem _interaction = default!; + + private SignalLinkCollection _linkCollection = new(); + + public override void Initialize() + { + base.Initialize(); + + _interaction = Get(); + + SubscribeLocalEvent(TransmitterStartupHandler); + SubscribeLocalEvent(TransmitterInteractUsingHandler); + SubscribeLocalEvent(OnTransmitterInvokePort); + + SubscribeLocalEvent(OnReceiverStartup); + SubscribeLocalEvent(OnReceiverInteractUsing); + + SubscribeLocalEvent(OnReceiverRemoved); + SubscribeLocalEvent(OnTransmitterRemoved); + } + + private void OnTransmitterRemoved(EntityUid uid, SignalTransmitterComponent component, ComponentRemove args) + { + _linkCollection.RemoveLinks(component); + } + + private void OnReceiverRemoved(EntityUid uid, SignalReceiverComponent component, ComponentRemove args) + { + _linkCollection.RemoveLinks(component); + } + + private void OnTransmitterInvokePort(EntityUid uid, SignalTransmitterComponent component, InvokePortEvent args) + { + if (!component.Outputs.TryGetPort(args.Port, out var port)) throw new PortNotFoundException(); + + if (args.Value == null) + { + if (port.Type != null && !port.Type.IsNullable()) throw new InvalidPortValueException(); + } + else + { + if (port.Type == null || !args.Value.GetType().IsAssignableTo(port.Type)) + throw new InvalidPortValueException(); + } + + port.Signal = args.Value; + + foreach (var link in _linkCollection.GetLinks(component, port.Name)) + { + if (!IsInRange(component, link.ReceiverComponent)) continue; + + RaiseLocalEvent(link.ReceiverComponent.Owner.Uid, + new SignalReceivedEvent(link.Receiverport.Name, args.Value)); + } + } + + private void OnReceiverInteractUsing(EntityUid uid, SignalReceiverComponent component, InteractUsingEvent args) + { + if (args.Handled) return; + + if (!args.Used.TryGetComponent(out var linker) || !linker.Port.HasValue || + !args.User.TryGetComponent(out ActorComponent? actor) || + !linker.Port.Value.transmitter.Outputs.TryGetPort(linker.Port.Value.port, out var port)) + { + return; + } + + if (component.Inputs.Count == 1) + { + LinkerInteraction(args.User, linker.Port.Value.transmitter, linker.Port.Value.port, component, + component.Inputs[0].Name); + args.Handled = true; + return; + } + + var bui = component.Owner.GetUIOrNull(SignalReceiverUiKey.Key); + if (bui == null) + { + return; + } + + bui.Open(actor.PlayerSession); + bui.SetState( + new SignalPortsState(new Dictionary(component.Inputs.GetValidatedPorts(port.Type)))); + args.Handled = true; + } + + private void OnReceiverStartup(EntityUid uid, SignalReceiverComponent component, ComponentStartup args) + { + if (component.Owner.GetUIOrNull(SignalReceiverUiKey.Key) is { } ui) + ui.OnReceiveMessage += msg => OnReceiverUIMessage(uid, component, msg); + } + + private void OnReceiverUIMessage(EntityUid uid, SignalReceiverComponent component, + ServerBoundUserInterfaceMessage msg) + { + switch (msg.Message) + { + case SignalPortSelected portSelected: + if (msg.Session.AttachedEntity == null || + !msg.Session.AttachedEntity.TryGetComponent(out HandsComponent? hands) || + !hands.TryGetActiveHeldEntity(out var heldEntity) || + !heldEntity.TryGetComponent(out SignalLinkerComponent? signalLinkerComponent) || + !_interaction.InRangeUnobstructed(msg.Session.AttachedEntity, component.Owner) || + !signalLinkerComponent.Port.HasValue || + !signalLinkerComponent.Port.Value.transmitter.Outputs.ContainsPort(signalLinkerComponent.Port + .Value.port) || !component.Inputs.ContainsPort(portSelected.Port)) + return; + LinkerInteraction(msg.Session.AttachedEntity, signalLinkerComponent.Port.Value.transmitter, + signalLinkerComponent.Port.Value.port, component, portSelected.Port); + break; + } + } + + private void TransmitterStartupHandler(EntityUid uid, SignalTransmitterComponent component, + ComponentStartup args) + { + if (component.Owner.GetUIOrNull(SignalTransmitterUiKey.Key) is { } ui) + ui.OnReceiveMessage += msg => OnTransmitterUIMessage(uid, component, msg); + + foreach (var portPrototype in component.Outputs) + { + if (portPrototype.Type == null) continue; + + var valueRequest = new SignalValueRequestedEvent(portPrototype.Name, portPrototype.Type); + RaiseLocalEvent(uid, valueRequest, false); + + if (!valueRequest.Handled) throw new NoSignalValueProvidedException(); + + portPrototype.Signal = valueRequest.Signal; + } + } + + private void OnTransmitterUIMessage(EntityUid uid, SignalTransmitterComponent component, + ServerBoundUserInterfaceMessage msg) + { + switch (msg.Message) + { + case SignalPortSelected portSelected: + if (msg.Session.AttachedEntity == null || + !msg.Session.AttachedEntity.TryGetComponent(out HandsComponent? hands) || + !hands.TryGetActiveHeldEntity(out var heldEntity) || + !heldEntity.TryGetComponent(out SignalLinkerComponent? signalLinkerComponent) || + !_interaction.InRangeUnobstructed(msg.Session.AttachedEntity, component.Owner)) + return; + LinkerSaveInteraction(msg.Session.AttachedEntity, signalLinkerComponent, component, + portSelected.Port); + break; + } + } + + private void TransmitterInteractUsingHandler(EntityUid uid, SignalTransmitterComponent component, + InteractUsingEvent args) + { + if (args.Handled) return; + + if (!args.Used.TryGetComponent(out var linker) || + !args.User.TryGetComponent(out ActorComponent? actor)) + { + return; + } + + if (component.Outputs.Count == 1) + { + var port = component.Outputs.First(); + LinkerSaveInteraction(args.User, linker, component, port.Name); + args.Handled = true; + return; + } + + var bui = component.Owner.GetUIOrNull(SignalTransmitterUiKey.Key); + if (bui == null) return; + bui.Open(actor.PlayerSession); + bui.SetState(new SignalPortsState(component.Outputs.GetPortStrings().ToArray())); + args.Handled = true; + } + + private void LinkerInteraction(IEntity entity, SignalTransmitterComponent transmitter, string transmitterPort, + SignalReceiverComponent receiver, string receiverPort) + { + if (_linkCollection.LinkExists(transmitter, transmitterPort, receiver, receiverPort)) + { + if (_linkCollection.RemoveLink(transmitter, transmitterPort, receiver, receiverPort)) + { + RaiseLocalEvent(receiver.Owner.Uid, new PortDisconnectedEvent(receiverPort)); + RaiseLocalEvent(transmitter.Owner.Uid, new PortDisconnectedEvent(transmitterPort)); + entity.PopupMessageCursor(Loc.GetString("signal-linker-component-unlinked-port", + ("port", receiverPort), ("machine", receiver))); + } + } + else + { + var tport = transmitter.Outputs.GetPort(transmitterPort); + var rport = receiver.Inputs.GetPort(receiverPort); + + if (!IsInRange(transmitter, receiver)) + { + entity.PopupMessageCursor(Loc.GetString("signal-linker-component-out-of-range")); + return; + } + + if (tport.MaxConnections != 0 && tport.MaxConnections >= _linkCollection.LinkCount(transmitter)) + { + entity.PopupMessageCursor(Loc.GetString("signal-linker-component-max-connections-transmitter")); + return; + } + + if (rport.MaxConnections != 0 && rport.MaxConnections <= _linkCollection.LinkCount(receiver)) + { + entity.PopupMessageCursor(Loc.GetString("signal-linker-component-max-connections-receiver")); + return; + } + + if (tport.Type != rport.Type) + { + entity.PopupMessageCursor(Loc.GetString("signal-linker-component-type-mismatch")); + return; + } + + var linkAttempt = new LinkAttemptEvent(entity, transmitter, transmitterPort, receiver, receiverPort); + RaiseLocalEvent(receiver.Owner.Uid, linkAttempt); + RaiseLocalEvent(transmitter.Owner.Uid, linkAttempt); + + if (linkAttempt.Cancelled) return; + + var link = _linkCollection.AddLink(transmitter, transmitterPort, receiver, receiverPort); + if (link.Transmitterport.Signal != null) + RaiseLocalEvent(receiver.Owner.Uid, + new SignalReceivedEvent(receiverPort, link.Transmitterport.Signal)); + + entity.PopupMessageCursor(Loc.GetString("signal-linker-component-linked-port", ("port", receiverPort), + ("machine", receiver))); + } + } + + private void LinkerSaveInteraction(IEntity entity, SignalLinkerComponent linkerComponent, + SignalTransmitterComponent transmitterComponent, string transmitterPort) + { + if (SavePortInSignalLinker(linkerComponent, transmitterComponent, transmitterPort)) + { + entity.PopupMessageCursor(Loc.GetString("signal-linker-component-saved-port", ("port", transmitterPort), + ("machine", transmitterComponent.Owner))); + } + } + + private bool SavePortInSignalLinker(SignalLinkerComponent linker, SignalTransmitterComponent transmitter, + string port) + { + if (!transmitter.Outputs.ContainsPort(port)) return false; + linker.Port = (transmitter, port); + return true; + } + + private bool IsInRange(SignalTransmitterComponent transmitterComponent, + SignalReceiverComponent receiverComponent) + { + if (transmitterComponent.Owner.TryGetComponent( + out var transmitterPowerReceiverComponent) && + receiverComponent.Owner.TryGetComponent( + out var receiverPowerReceiverComponent) + ) //&& todo are they on the same powernet? + { + return true; + } + + return transmitterComponent.Owner.Transform.MapPosition.InRange( + receiverComponent.Owner.Transform.MapPosition, 30f); + } + } +} diff --git a/Content.Server/MachineLinking/System/SignalSwitchSystem.cs b/Content.Server/MachineLinking/System/SignalSwitchSystem.cs new file mode 100644 index 0000000000..79e75de8a0 --- /dev/null +++ b/Content.Server/MachineLinking/System/SignalSwitchSystem.cs @@ -0,0 +1,35 @@ +using Content.Server.MachineLinking.Components; +using Content.Server.MachineLinking.Events; +using Content.Shared.Interaction; +using Robust.Shared.GameObjects; + +namespace Content.Server.MachineLinking.System +{ + public class SignalSwitchSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteracted); + SubscribeLocalEvent(OnSignalValueRequested); + } + + private void OnSignalValueRequested(EntityUid uid, SignalSwitchComponent component, SignalValueRequestedEvent args) + { + if (args.Port == "state") + { + args.Handled = true; + args.Signal = component.State; + } + } + + private void OnInteracted(EntityUid uid, SignalSwitchComponent component, InteractHandEvent args) + { + component.State = !component.State; + RaiseLocalEvent(uid, new InvokePortEvent("state", component.State), false); + RaiseLocalEvent(uid, new InvokePortEvent("stateChange"), false); + args.Handled = true; + } + } +} diff --git a/Content.Server/MachineLinking/System/TwoWayLeverSystem.cs b/Content.Server/MachineLinking/System/TwoWayLeverSystem.cs new file mode 100644 index 0000000000..f1fea0b3fe --- /dev/null +++ b/Content.Server/MachineLinking/System/TwoWayLeverSystem.cs @@ -0,0 +1,54 @@ +using System; +using Content.Server.MachineLinking.Components; +using Content.Server.MachineLinking.Events; +using Content.Shared.Interaction; +using Content.Shared.MachineLinking; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Server.MachineLinking.System +{ + public class TwoWayLeverSystem : EntitySystem + { + [Dependency] private readonly IComponentManager _componentManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnSignalValueRequested); + } + + private void OnSignalValueRequested(EntityUid uid, TwoWayLeverComponent component, SignalValueRequestedEvent args) + { + args.Signal = component.State; + args.Handled = true; + } + + private void OnInteractHand(EntityUid uid, TwoWayLeverComponent component, InteractHandEvent args) + { + component.State = component.State switch + { + TwoWayLeverSignal.Middle => component.NextSignalLeft ? TwoWayLeverSignal.Left : TwoWayLeverSignal.Right, + TwoWayLeverSignal.Right => TwoWayLeverSignal.Middle, + TwoWayLeverSignal.Left => TwoWayLeverSignal.Middle, + _ => throw new ArgumentOutOfRangeException() + }; + + if (component.State == TwoWayLeverSignal.Middle) + { + component.NextSignalLeft = !component.NextSignalLeft; + } + + if (_componentManager.TryGetComponent(uid, out var appearanceComponent)) + { + appearanceComponent.SetData(TwoWayLeverVisuals.State, component.State); + } + + RaiseLocalEvent(uid, new InvokePortEvent("state", component.State)); + args.Handled = true; + } + } +} diff --git a/Content.Shared/MachineLinking/TwoWayLeverSignal.cs b/Content.Shared/MachineLinking/TwoWayLeverSignal.cs index 9ab282bc08..7bfb147079 100644 --- a/Content.Shared/MachineLinking/TwoWayLeverSignal.cs +++ b/Content.Shared/MachineLinking/TwoWayLeverSignal.cs @@ -13,7 +13,7 @@ namespace Content.Shared.MachineLinking public enum TwoWayLeverSignal : byte { Middle, - Left, - Right + Right, + Left } } diff --git a/Content.Shared/MachineLinking/UIKeys.cs b/Content.Shared/MachineLinking/UIKeys.cs new file mode 100644 index 0000000000..eb1e3552e9 --- /dev/null +++ b/Content.Shared/MachineLinking/UIKeys.cs @@ -0,0 +1,17 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.MachineLinking +{ + [Serializable, NetSerializable] + public enum SignalTransmitterUiKey + { + Key + } + + [Serializable, NetSerializable] + public enum SignalReceiverUiKey + { + Key + } +} diff --git a/Content.Shared/MachineLinking/UIMessages.cs b/Content.Shared/MachineLinking/UIMessages.cs new file mode 100644 index 0000000000..c5dd63f6d4 --- /dev/null +++ b/Content.Shared/MachineLinking/UIMessages.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.MachineLinking +{ + [Serializable, NetSerializable] + public class SignalPortsState : BoundUserInterfaceState + { + /// + /// A Dictionary containing all ports and wether or not they can be selected. + /// + public readonly Dictionary Ports; + + public SignalPortsState(string[] ports) : this(ports.ToDictionary(s => s, _ => true)) + { + } + + public SignalPortsState(Dictionary ports) + { + Ports = ports; + } + } + + [Serializable, NetSerializable] + public class SignalPortSelected : BoundUserInterfaceMessage + { + public readonly string Port; + + public SignalPortSelected(string port) + { + Port = port; + } + } +} diff --git a/Resources/Locale/en-US/conveyors/conveyor-component.ftl b/Resources/Locale/en-US/conveyors/conveyor-component.ftl new file mode 100644 index 0000000000..c90ccdd48f --- /dev/null +++ b/Resources/Locale/en-US/conveyors/conveyor-component.ftl @@ -0,0 +1 @@ +conveyor-component-failed-link = The port shocks you as you try to connect to it! diff --git a/Resources/Locale/en-US/machine-linking/components/signal-button-component.ftl b/Resources/Locale/en-US/machine-linking/components/signal-button-component.ftl deleted file mode 100644 index dd9967534f..0000000000 --- a/Resources/Locale/en-US/machine-linking/components/signal-button-component.ftl +++ /dev/null @@ -1,2 +0,0 @@ -signal-button-component-transmit-signal-success-message = Click -signal-button-component-transmit-signal-no-receivers-message = No receivers connected. \ No newline at end of file diff --git a/Resources/Locale/en-US/machine-linking/components/signal-linker-component.ftl b/Resources/Locale/en-US/machine-linking/components/signal-linker-component.ftl new file mode 100644 index 0000000000..fa671b55c7 --- /dev/null +++ b/Resources/Locale/en-US/machine-linking/components/signal-linker-component.ftl @@ -0,0 +1,10 @@ +signal-linker-component-saved-port = Successfully saved port '{$port}' of {$machine}! +signal-linker-component-linked-port = Successfully linked saved port to port '{$port}' of {$machine}! +signal-linker-component-unlinked-port = Successfully unlinked saved port from port '{$port}' of {$machine}! + +signal-linker-component-max-connections-receiver = Maximum connections reached on the receiver! +signal-linker-component-max-connections-transmitter = Maximum connections reached on the transmitter! + +signal-linker-component-type-mismatch = The port's type does not match the type of the saved port! + +signal-linker-component-out-of-range = Connection is out of range! diff --git a/Resources/Locale/en-US/machine-linking/components/signal-receiver-component.ftl b/Resources/Locale/en-US/machine-linking/components/signal-receiver-component.ftl deleted file mode 100644 index ba1fad71c6..0000000000 --- a/Resources/Locale/en-US/machine-linking/components/signal-receiver-component.ftl +++ /dev/null @@ -1,5 +0,0 @@ -signal-receiver-component-interact-no-transmitter-message = Signal not set. -signal-receiver-component-interact-unlinked = Unlinked. -signal-receiver-component-interact-out-of-range = Out of range. -signal-receiver-component-interact-max-transmitters-limit = Max Transmitters reached! -signal-receiver-component-interact-success = Linked! \ No newline at end of file diff --git a/Resources/Locale/en-US/machine-linking/components/signal-switch-component.ftl b/Resources/Locale/en-US/machine-linking/components/signal-switch-component.ftl deleted file mode 100644 index cbd46503de..0000000000 --- a/Resources/Locale/en-US/machine-linking/components/signal-switch-component.ftl +++ /dev/null @@ -1,4 +0,0 @@ -signal-switch-component-transmit-no-receivers-connected = No receivers connected. - -# ToggleSwitchVerb -toggle-switch-verb-get-data-text = Toggle Switch \ No newline at end of file diff --git a/Resources/Locale/en-US/machine-linking/components/signal-transmitter-component.ftl b/Resources/Locale/en-US/machine-linking/components/signal-transmitter-component.ftl deleted file mode 100644 index ed2654f0d5..0000000000 --- a/Resources/Locale/en-US/machine-linking/components/signal-transmitter-component.ftl +++ /dev/null @@ -1 +0,0 @@ -signal-transmitter-component-get-signal-success = Signal fetched. \ No newline at end of file diff --git a/Resources/Locale/en-US/machine-linking/components/signal-two-way-lever-component.ftl b/Resources/Locale/en-US/machine-linking/components/signal-two-way-lever-component.ftl deleted file mode 100644 index 70c79f0b82..0000000000 --- a/Resources/Locale/en-US/machine-linking/components/signal-two-way-lever-component.ftl +++ /dev/null @@ -1 +0,0 @@ -signal-two-way-lever-component-next-state-no-receivers-connected = No receivers connected. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/lighting.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/lighting.yml index 744655a91b..36ed449da4 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/lighting.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/lighting.yml @@ -28,6 +28,12 @@ softness: 1.1 offset: "0, -0.5" - type: SignalReceiver + inputs: + - name: toggle + - type: UserInterface + interfaces: + - key: enum.SignalReceiverUiKey.Key + type: SignalPortSelectorBoundUserInterface - type: Damageable resistances: metallicResistances - type: Destructible diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml index 66abc219af..710ac85c3c 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml @@ -12,6 +12,14 @@ - type: SignalSwitch - type: Rotatable - type: SignalTransmitter + outputs: + - name: state + type: bool + - name: stateChange + - type: UserInterface + interfaces: + - key: enum.SignalTransmitterUiKey.Key + type: SignalPortSelectorBoundUserInterface placement: snap: - Wallmount @@ -30,6 +38,8 @@ - type: SignalButton - type: Rotatable - type: SignalTransmitter + outputs: + - name: pressed placement: snap: - Wallmount diff --git a/Resources/Prototypes/Entities/Structures/conveyor.yml b/Resources/Prototypes/Entities/Structures/conveyor.yml index 1dc2a9088f..a93d889ddf 100644 --- a/Resources/Prototypes/Entities/Structures/conveyor.yml +++ b/Resources/Prototypes/Entities/Structures/conveyor.yml @@ -26,7 +26,10 @@ state: conveyor_started_cw drawdepth: FloorObjects - type: SignalReceiver - maxTransmitters: 1 + inputs: + - name: state + type: Content.Shared.MachineLinking.TwoWayLeverSignal + maxConnections: 1 - type: ApcPowerReceiver - type: Conveyor - type: Appearance @@ -69,6 +72,10 @@ sprite: Structures/conveyor.rsi state: switch-off - type: TwoWayLever + - type: SignalTransmitter + outputs: + - name: state + type: Content.Shared.MachineLinking.TwoWayLeverSignal - type: Appearance visuals: - type: TwoWayLeverVisualizer