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