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 <ritter.paul1+git@googlemail.com>
This commit is contained in:
Paul Ritter
2021-08-27 17:46:02 +02:00
committed by GitHub
parent 4a68032ea1
commit e11a9b282a
47 changed files with 1059 additions and 633 deletions

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,4 @@
<customControls:SS14Window xmlns:customControls="https://spacestation14.io"
Title="Port Selector" MinSize="200 200">
<customControls:ItemList Name="ButtonContainer" VerticalExpand="True" HorizontalExpand="True" SelectMode="Button"/>
</customControls:SS14Window>

View File

@@ -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);
}
}
}
}

View File

@@ -14,7 +14,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Conveyor namespace Content.Server.Conveyor
{ {
[RegisterComponent] [RegisterComponent]
public class ConveyorComponent : Component, ISignalReceiver<TwoWayLeverSignal>, ISignalReceiver<bool> public class ConveyorComponent : Component
{ {
public override string Name => "Conveyor"; public override string Name => "Conveyor";
@@ -150,7 +150,7 @@ namespace Content.Server.Conveyor
return true; return true;
} }
public void TriggerSignal(TwoWayLeverSignal signal) public void SetState(TwoWayLeverSignal signal)
{ {
State = signal switch State = signal switch
{ {
@@ -160,10 +160,5 @@ namespace Content.Server.Conveyor
_ => ConveyorState.Off _ => ConveyorState.Off
}; };
} }
public void TriggerSignal(bool signal)
{
State = signal ? ConveyorState.Forward : ConveyorState.Off;
}
} }
} }

View File

@@ -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<ConveyorComponent, SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<ConveyorComponent, PortDisconnectedEvent>(OnPortDisconnected);
SubscribeLocalEvent<ConveyorComponent, LinkAttemptEvent>(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<StunnableComponent>(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;
}
}
}
}

View File

@@ -3,8 +3,6 @@ using System.Threading.Tasks;
using Content.Server.Ghost; using Content.Server.Ghost;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Items; using Content.Server.Items;
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Signals;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Temperature.Components; using Content.Server.Temperature.Components;
using Content.Shared.Actions.Behaviors; using Content.Shared.Actions.Behaviors;
@@ -12,7 +10,6 @@ using Content.Shared.Damage;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Light; using Content.Shared.Light;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Server.GameObjects; 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. /// Component that represents a wall light. It has a light bulb that can be replaced when broken.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
public class PoweredLightComponent : Component, IInteractHand, IInteractUsing, IMapInit, ISignalReceiver<bool>, ISignalReceiver<ToggleSignal>, IGhostBooAffected public class PoweredLightComponent : Component, IInteractHand, IInteractUsing, IMapInit, IGhostBooAffected
{ {
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -309,13 +306,7 @@ namespace Content.Server.Light.Components
UpdateLight(); UpdateLight();
} }
public void TriggerSignal(bool signal) public void ToggleLight()
{
_on = signal;
UpdateLight();
}
public void TriggerSignal(ToggleSignal signal)
{ {
_on = !_on; _on = !_on;
UpdateLight(); UpdateLight();

View File

@@ -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<PoweredLightComponent, SignalReceivedEvent>(OnSignalReceived);
}
private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, SignalReceivedEvent args)
{
switch (args.Port)
{
case "toggle":
component.ToggleLight();
break;
}
}
}
}

View File

@@ -1,7 +0,0 @@
namespace Content.Server.MachineLinking.Components
{
public interface ISignalReceiver<in T>
{
void TriggerSignal(T signal);
}
}

View File

@@ -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.GameObjects;
using Robust.Shared.Localization;
namespace Content.Server.MachineLinking.Components namespace Content.Server.MachineLinking.Components
{ {
[RegisterComponent] [RegisterComponent]
public class SignalButtonComponent : Component, IActivate, IInteractHand public class SignalButtonComponent : Component
{ {
public override string Name => "SignalButton"; 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<SignalTransmitterComponent>(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"));
}
}
} }
} }

View File

@@ -9,13 +9,6 @@ namespace Content.Server.MachineLinking.Components
public override string Name => "SignalLinker"; public override string Name => "SignalLinker";
[ViewVariables] [ViewVariables]
public SignalTransmitterComponent? Link { get; set; } public (SignalTransmitterComponent transmitter, string port)? Port;
protected override void Initialize()
{
base.Initialize();
Link = null;
}
} }
} }

View File

@@ -1,125 +1,21 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using Content.Server.MachineLinking.Models;
using Content.Server.Tools.Components;
using Content.Shared.Interaction;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Content.Shared.Tool;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.MachineLinking.Components namespace Content.Server.MachineLinking.Components
{ {
[RegisterComponent] [RegisterComponent]
public class SignalReceiverComponent : Component, IInteractUsing public class SignalReceiverComponent : Component
{ {
public override string Name => "SignalReceiver"; public override string Name => "SignalReceiver";
private readonly List<SignalTransmitterComponent> _transmitters = new(); [DataField("inputs")]
private List<SignalPort> _inputs = new();
[DataField("maxTransmitters")] [ViewVariables]
private int? _maxTransmitters = default; public IReadOnlyList<SignalPort> Inputs => _inputs;
public void DistributeSignal<T>(T state)
{
foreach (var comp in Owner.GetAllComponents<ISignalReceiver<T>>())
{
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);
}
}
/// <summary>
/// Subscribes/Unsubscribes a transmitter to this component. Returns whether it was successful.
/// </summary>
/// <param name="user"></param>
/// <param name="transmitter"></param>
/// <returns></returns>
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<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
return false;
if (tool.HasQuality(ToolQuality.Multitool)
&& eventArgs.Using.TryGetComponent<SignalLinkerComponent>(out var linker))
{
return Interact(eventArgs.User, linker.Link);
}
return false;
}
protected override void Shutdown()
{
base.Shutdown();
UnsubscribeAll();
_transmitters.Clear();
}
} }
} }

View File

@@ -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.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.MachineLinking.Components namespace Content.Server.MachineLinking.Components
{ {
[RegisterComponent] [RegisterComponent]
public class SignalSwitchComponent : Component, IInteractHand, IActivate public class SignalSwitchComponent : Component
{ {
public override string Name => "SignalSwitch"; public override string Name => "SignalSwitch";
[DataField("on")] public bool State;
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<SignalTransmitterComponent>(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<SignalSwitchComponent>
{
protected override void Activate(IEntity user, SignalSwitchComponent component)
{
component.TransmitSignal(user);
}
protected override void GetData(IEntity user, SignalSwitchComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString("toggle-switch-verb-get-data-text");
data.Visibility = VerbVisibility.Visible;
}
}
} }
} }

View File

@@ -1,159 +1,21 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using Content.Server.MachineLinking.Models;
using Content.Server.Tools.Components;
using Content.Shared.Interaction;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers;
using Content.Shared.Tool;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Server.MachineLinking.Components namespace Content.Server.MachineLinking.Components
{ {
[RegisterComponent] [RegisterComponent]
public class SignalTransmitterComponent : Component, IInteractUsing, ISerializationHooks public class SignalTransmitterComponent : Component
{ {
public override string Name => "SignalTransmitter"; public override string Name => "SignalTransmitter";
private List<SignalReceiverComponent>? _unresolvedReceivers = new(); [DataField("outputs")]
private List<SignalReceiverComponent> _receivers = new(); private List<SignalPort> _outputs = new();
/// <summary>
/// 0 is unlimited range
/// </summary>
[ViewVariables] [ViewVariables]
[DataField("range")] public IReadOnlyList<SignalPort> Outputs => _outputs;
public float Range { get; private set; } = 10;
[DataField("signalReceivers")] private List<EntityUid> _receiverIds = new();
void ISerializationHooks.BeforeSerialization()
{
var entityList = new List<EntityUid>();
foreach (var receiver in _receivers)
{
if (receiver.Deleted)
{
continue;
}
entityList.Add(receiver.Owner.Uid);
}
_receiverIds = entityList;
}
void ISerializationHooks.AfterDeserialization()
{
_unresolvedReceivers = new List<SignalReceiverComponent>();
foreach (var id in _receiverIds)
{
if (!Owner.EntityManager.TryGetEntity(id, out var entity) ||
!entity.TryGetComponent<SignalReceiverComponent>(out var receiver))
{
continue;
}
_unresolvedReceivers.Add(receiver);
}
}
protected override void Initialize()
{
base.Initialize();
_receivers = new List<SignalReceiverComponent>();
if (_unresolvedReceivers != null)
{
foreach (var receiver in _unresolvedReceivers)
{
receiver.Subscribe(this);
}
_unresolvedReceivers = null;
}
}
public bool TransmitSignal<T>(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<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
return false;
if (tool.HasQuality(ToolQuality.Multitool)
&& eventArgs.Using.TryGetComponent<SignalLinkerComponent>(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();
}
} }
} }

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Content.Server.MachineLinking.Exceptions
{
public class InvalidPortValueException : Exception
{
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Content.Server.MachineLinking.Exceptions
{
public class LinkAlreadyRegisteredException : Exception
{
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Content.Server.MachineLinking.Exceptions
{
public class NoSignalValueProvidedException : Exception
{
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Content.Server.MachineLinking.Exceptions
{
public class PortNotFoundException : Exception
{
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<SignalTransmitterComponent, List<SignalLink>> _transmitterDict = new();
private Dictionary<SignalReceiverComponent, List<SignalLink>> _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<SignalLink> 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;
}
}
}
}

View File

@@ -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; }
/// <summary>
/// Maximum connections of the port. 0 means infinite.
/// </summary>
[DataField("maxConnections")] public int MaxConnections { get; } = 0;
public object? Signal;
}
public static class PortPrototypeExtensions{
public static bool ContainsPort(this IReadOnlyList<SignalPort> ports, string port)
{
foreach (var portPrototype in ports)
{
if (portPrototype.Name == port)
{
return true;
}
}
return false;
}
public static IEnumerable<string> GetPortStrings(this IReadOnlyList<SignalPort> ports)
{
foreach (var portPrototype in ports)
{
yield return portPrototype.Name;
}
}
public static IEnumerable<KeyValuePair<string, bool>> GetValidatedPorts(this IReadOnlyList<SignalPort> ports, Type? validType)
{
foreach (var portPrototype in ports)
{
yield return new KeyValuePair<string, bool>(portPrototype.Name, portPrototype.Type == validType);
}
}
public static bool TryGetPort(this IReadOnlyList<SignalPort> 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<SignalPort> ports, string name)
{
foreach (var portPrototype in ports)
{
if (portPrototype.Name == name)
{
return portPrototype;
}
}
throw new PortNotFoundException();
}
}
}

View File

@@ -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<IEntitySystemManager>().TryGetEntitySystem<SignalLinkerSystem>(out var system))
{
return;
}
var ret = system.SignalLinkerKeybind(player.UserId, enable);
shell.WriteLine(ret ? "Enabled" : "Disabled");
}
}
}

View File

@@ -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<NetUserId, SignalTransmitterComponent?> _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<SignalLinkerSystem>();
}
_transmitters.Add(id, null);
}
else
{
if (!_transmitters.ContainsKey(id))
{
return false;
}
_transmitters.Remove(id);
if (_transmitters.Count == 0)
{
CommandBinds.Unregister<SignalLinkerSystem>();
}
}
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<SignalReceiverComponent>(out var signalReceiver))
{
if (signalReceiver.Interact(session.AttachedEntity, signalTransmitter))
{
return true;
}
}
if (entity.TryGetComponent<SignalTransmitterComponent>(out var transmitter))
{
_transmitters[session.UserId] = transmitter.GetSignal(session.AttachedEntity);
return true;
}
return false;
}
}
}

View File

@@ -1,4 +0,0 @@
namespace Content.Server.MachineLinking.Signals
{
public struct ToggleSignal {}
}

View File

@@ -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<SignalButtonComponent, InteractHandEvent>(OnInteractHand);
}
private void OnInteractHand(EntityUid uid, SignalButtonComponent component, InteractHandEvent args)
{
RaiseLocalEvent(uid, new InvokePortEvent("pressed"), false);
args.Handled = true;
}
}
}

View File

@@ -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<InteractionSystem>();
SubscribeLocalEvent<SignalTransmitterComponent, ComponentStartup>(TransmitterStartupHandler);
SubscribeLocalEvent<SignalTransmitterComponent, InteractUsingEvent>(TransmitterInteractUsingHandler);
SubscribeLocalEvent<SignalTransmitterComponent, InvokePortEvent>(OnTransmitterInvokePort);
SubscribeLocalEvent<SignalReceiverComponent, ComponentStartup>(OnReceiverStartup);
SubscribeLocalEvent<SignalReceiverComponent, InteractUsingEvent>(OnReceiverInteractUsing);
SubscribeLocalEvent<SignalReceiverComponent, ComponentRemove>(OnReceiverRemoved);
SubscribeLocalEvent<SignalTransmitterComponent, ComponentRemove>(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<SignalLinkerComponent>(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<string, bool>(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<SignalLinkerComponent>(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<ApcPowerReceiverComponent>(
out var transmitterPowerReceiverComponent) &&
receiverComponent.Owner.TryGetComponent<ApcPowerReceiverComponent>(
out var receiverPowerReceiverComponent)
) //&& todo are they on the same powernet?
{
return true;
}
return transmitterComponent.Owner.Transform.MapPosition.InRange(
receiverComponent.Owner.Transform.MapPosition, 30f);
}
}
}

View File

@@ -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<SignalSwitchComponent, InteractHandEvent>(OnInteracted);
SubscribeLocalEvent<SignalSwitchComponent, SignalValueRequestedEvent>(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;
}
}
}

View File

@@ -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<TwoWayLeverComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<TwoWayLeverComponent, SignalValueRequestedEvent>(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<AppearanceComponent>(uid, out var appearanceComponent))
{
appearanceComponent.SetData(TwoWayLeverVisuals.State, component.State);
}
RaiseLocalEvent(uid, new InvokePortEvent("state", component.State));
args.Handled = true;
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Content.Shared.MachineLinking
public enum TwoWayLeverSignal : byte public enum TwoWayLeverSignal : byte
{ {
Middle, Middle,
Left, Right,
Right Left
} }
} }

View File

@@ -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
}
}

View File

@@ -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
{
/// <summary>
/// A Dictionary containing all ports and wether or not they can be selected.
/// </summary>
public readonly Dictionary<string, bool> Ports;
public SignalPortsState(string[] ports) : this(ports.ToDictionary(s => s, _ => true))
{
}
public SignalPortsState(Dictionary<string, bool> ports)
{
Ports = ports;
}
}
[Serializable, NetSerializable]
public class SignalPortSelected : BoundUserInterfaceMessage
{
public readonly string Port;
public SignalPortSelected(string port)
{
Port = port;
}
}
}

View File

@@ -0,0 +1 @@
conveyor-component-failed-link = The port shocks you as you try to connect to it!

View File

@@ -1,2 +0,0 @@
signal-button-component-transmit-signal-success-message = Click
signal-button-component-transmit-signal-no-receivers-message = No receivers connected.

View File

@@ -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!

View File

@@ -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!

View File

@@ -1,4 +0,0 @@
signal-switch-component-transmit-no-receivers-connected = No receivers connected.
# ToggleSwitchVerb
toggle-switch-verb-get-data-text = Toggle Switch

View File

@@ -1 +0,0 @@
signal-transmitter-component-get-signal-success = Signal fetched.

View File

@@ -1 +0,0 @@
signal-two-way-lever-component-next-state-no-receivers-connected = No receivers connected.

View File

@@ -28,6 +28,12 @@
softness: 1.1 softness: 1.1
offset: "0, -0.5" offset: "0, -0.5"
- type: SignalReceiver - type: SignalReceiver
inputs:
- name: toggle
- type: UserInterface
interfaces:
- key: enum.SignalReceiverUiKey.Key
type: SignalPortSelectorBoundUserInterface
- type: Damageable - type: Damageable
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible

View File

@@ -12,6 +12,14 @@
- type: SignalSwitch - type: SignalSwitch
- type: Rotatable - type: Rotatable
- type: SignalTransmitter - type: SignalTransmitter
outputs:
- name: state
type: bool
- name: stateChange
- type: UserInterface
interfaces:
- key: enum.SignalTransmitterUiKey.Key
type: SignalPortSelectorBoundUserInterface
placement: placement:
snap: snap:
- Wallmount - Wallmount
@@ -30,6 +38,8 @@
- type: SignalButton - type: SignalButton
- type: Rotatable - type: Rotatable
- type: SignalTransmitter - type: SignalTransmitter
outputs:
- name: pressed
placement: placement:
snap: snap:
- Wallmount - Wallmount

View File

@@ -26,7 +26,10 @@
state: conveyor_started_cw state: conveyor_started_cw
drawdepth: FloorObjects drawdepth: FloorObjects
- type: SignalReceiver - type: SignalReceiver
maxTransmitters: 1 inputs:
- name: state
type: Content.Shared.MachineLinking.TwoWayLeverSignal
maxConnections: 1
- type: ApcPowerReceiver - type: ApcPowerReceiver
- type: Conveyor - type: Conveyor
- type: Appearance - type: Appearance
@@ -69,6 +72,10 @@
sprite: Structures/conveyor.rsi sprite: Structures/conveyor.rsi
state: switch-off state: switch-off
- type: TwoWayLever - type: TwoWayLever
- type: SignalTransmitter
outputs:
- name: state
type: Content.Shared.MachineLinking.TwoWayLeverSignal
- type: Appearance - type: Appearance
visuals: visuals:
- type: TwoWayLeverVisualizer - type: TwoWayLeverVisualizer