Machine Linking Overhaul (#7160)

This commit is contained in:
Jack Fox
2022-04-04 01:13:03 -05:00
committed by GitHub
parent fd7ea3f1e7
commit f957c58906
40 changed files with 605 additions and 696 deletions

View File

@@ -34,7 +34,7 @@ namespace Content.Client.Conveyor.Visualizers
{
ConveyorState.Off => _stateStopped,
ConveyorState.Forward => _stateRunning,
ConveyorState.Reversed => _stateReversed,
ConveyorState.Reverse => _stateReversed,
_ => throw new ArgumentOutOfRangeException()
};

View File

@@ -27,13 +27,13 @@ namespace Content.Client.Conveyor.Visualizers
return;
}
appearance.TryGetData(TwoWayLeverVisuals.State, out TwoWayLeverSignal state);
appearance.TryGetData(TwoWayLeverVisuals.State, out TwoWayLeverState state);
var texture = state switch
{
TwoWayLeverSignal.Middle => _stateOff,
TwoWayLeverSignal.Right => _stateForward,
TwoWayLeverSignal.Left => _stateReversed,
TwoWayLeverState.Middle => _stateOff,
TwoWayLeverState.Right => _stateForward,
TwoWayLeverState.Left => _stateReversed,
_ => _stateOff
};

View File

@@ -275,12 +275,12 @@ namespace Content.Client.Entry
"Uplink",
"SpawnItemsOnUse",
"AmbientOnPowered",
"DoorSignalControl",
"Wieldable",
"IncreaseDamageOnWield",
"TabletopGame",
"LitOnPowered",
"TriggerOnSignalReceived",
"ToggleDoorOnTrigger",
"DeviceNetworkComponent",
"WiredNetworkConnection",
"WirelessNetworkConnection",

View File

@@ -9,9 +9,10 @@ namespace Content.Client.MachineLinking.UI
{
private SignalPortSelectorMenu? _menu;
public SignalPortSelectorBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
{
}
private string? _selectedTransmitterPort;
private string? _selectedReceiverPort;
public SignalPortSelectorBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey) { }
protected override void Open()
{
@@ -29,13 +30,46 @@ namespace Content.Client.MachineLinking.UI
{
case SignalPortsState data:
_menu?.UpdateState(data);
_selectedTransmitterPort = null;
_selectedReceiverPort = null;
break;
}
}
public void OnPortSelected(string port)
public void OnTransmitterPortSelected(string port)
{
SendMessage(new SignalPortSelected(port));
_selectedTransmitterPort = port;
if (_selectedReceiverPort != null)
{
SendMessage(new SignalPortSelected(_selectedTransmitterPort, _selectedReceiverPort));
_selectedTransmitterPort = null;
_selectedReceiverPort = null;
}
}
public void OnReceiverPortSelected(string port)
{
_selectedReceiverPort = port;
if (_selectedTransmitterPort != null)
{
SendMessage(new SignalPortSelected(_selectedTransmitterPort, _selectedReceiverPort));
_selectedTransmitterPort = null;
_selectedReceiverPort = null;
}
}
public void OnClearPressed()
{
_selectedTransmitterPort = null;
_selectedReceiverPort = null;
SendMessage(new LinkerClearSelected());
}
public void OnLinkDefaultPressed()
{
_selectedTransmitterPort = null;
_selectedReceiverPort = null;
SendMessage(new LinkerLinkDefaultSelected());
}
protected override void Dispose(bool disposing)

View File

@@ -1,4 +1,19 @@
<customControls:DefaultWindow xmlns:customControls="https://spacestation14.io"
Title="Port Selector" MinSize="200 200">
<customControls:ItemList Name="ButtonContainer" VerticalExpand="True" HorizontalExpand="True" SelectMode="Button"/>
</customControls:DefaultWindow>
<DefaultWindow xmlns="https://spacestation14.io" Title="Port Selector" MinSize="400 200">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.3">
<RichTextLabel Name="HeaderLeft"/>
<BoxContainer Name="ButtonContainerLeft" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"/>
</BoxContainer>
<BoxContainer Name="ContainerMiddle" Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.2"/>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.3">
<RichTextLabel Name="HeaderRight"/>
<BoxContainer Name="ButtonContainerRight" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Button Name="ButtonClear" Text="Clear"/>
<Button Name="ButtonLinkDefault" Text="Link Defaults"/>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -1,9 +1,10 @@
using System.Collections.Generic;
using Content.Shared.MachineLinking;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Graphics;
namespace Content.Client.MachineLinking.UI
{
@@ -11,25 +12,67 @@ namespace Content.Client.MachineLinking.UI
public sealed partial class SignalPortSelectorMenu : DefaultWindow
{
private SignalPortSelectorBoundUserInterface _bui;
private LinksRender links;
private ButtonGroup buttonGroup = new();
public SignalPortSelectorMenu(SignalPortSelectorBoundUserInterface boundUserInterface)
{
RobustXamlLoader.Load(this);
_bui = boundUserInterface;
links = new(ButtonContainerLeft, ButtonContainerRight);
ContainerMiddle.AddChild(links);
ButtonClear.OnPressed += _ => _bui.OnClearPressed();
ButtonLinkDefault.OnPressed += _ => _bui.OnLinkDefaultPressed();
}
public void UpdateState(SignalPortsState state)
{
ButtonContainer.Clear();
foreach (var port in state.Ports)
HeaderLeft.SetMessage(state.TransmitterName);
ButtonContainerLeft.DisposeAllChildren();
foreach (var port in state.TransmitterPorts)
{
var portBtn = new ItemList.Item(ButtonContainer)
var portButton = new Button() { Text = port, ToggleMode = true, Group = buttonGroup };
portButton.OnPressed += _ => _bui.OnTransmitterPortSelected(port);
ButtonContainerLeft.AddChild(portButton);
}
HeaderRight.SetMessage(state.ReceiverName);
ButtonContainerRight.DisposeAllChildren();
foreach (var port in state.ReceiverPorts)
{
var portButton = new Button() { Text = port, ToggleMode = true, Group = buttonGroup };
portButton.OnPressed += _ => _bui.OnReceiverPortSelected(port);
ButtonContainerRight.AddChild(portButton);
}
links.Links = state.Links;
}
private sealed class LinksRender : Control
{
public List<(int, int)> Links = new();
public BoxContainer LeftButton;
public BoxContainer RightButton;
public LinksRender(BoxContainer leftButton, BoxContainer rightButton)
{
LeftButton = leftButton;
RightButton = rightButton;
}
protected override void Draw(DrawingHandleScreen handle)
{
var leftOffset = LeftButton.PixelPosition.Y;
var rightOffset = RightButton.PixelPosition.Y;
foreach (var (left, right) in Links)
{
Text = port.Key,
Disabled = !port.Value
};
portBtn.OnSelected += _ => _bui.OnPortSelected(port.Key);
ButtonContainer.Add(portBtn);
var leftChild = LeftButton.GetChild(left);
var rightChild = RightButton.GetChild(right);
var y1 = leftChild.PixelPosition.Y + leftChild.PixelHeight / 2 + leftOffset;
var y2 = rightChild.PixelPosition.Y + rightChild.PixelHeight / 2 + rightOffset;
handle.DrawLine((0, y1), (PixelWidth, y2), Color.Cyan);
}
}
}
}

View File

@@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Server.MachineLinking.Models;
using Content.Server.Power.Components;
using Content.Server.Recycling;
using Content.Server.Recycling.Components;
using Content.Server.Stunnable;
using Content.Shared.Conveyor;
using Content.Shared.Item;
using Content.Shared.MachineLinking;
using Content.Shared.Movement.Components;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
@@ -23,18 +20,24 @@ namespace Content.Server.Conveyor
public sealed class ConveyorSystem : EntitySystem
{
[Dependency] private RecyclerSystem _recycler = default!;
[Dependency] private StunSystem _stunSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ConveyorComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ConveyorComponent, SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<ConveyorComponent, PortDisconnectedEvent>(OnPortDisconnected);
SubscribeLocalEvent<ConveyorComponent, LinkAttemptEvent>(OnLinkAttempt);
SubscribeLocalEvent<ConveyorComponent, PowerChangedEvent>(OnPowerChanged);
}
private void OnInit(EntityUid uid, ConveyorComponent component, ComponentInit args)
{
var receiver = EnsureComp<SignalReceiverComponent>(uid);
foreach (string port in Enum.GetNames<ConveyorState>())
if (!receiver.Inputs.ContainsKey(port))
receiver.AddPort(port);
}
private void OnPowerChanged(EntityUid uid, ConveyorComponent component, PowerChangedEvent args)
{
UpdateAppearance(component);
@@ -55,41 +58,15 @@ namespace Content.Server.Conveyor
}
}
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();
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true);
component.Owner.PopupMessage(args.Attemptee, Loc.GetString("conveyor-component-failed-link"));
}
}
private void OnPortDisconnected(EntityUid uid, ConveyorComponent component, PortDisconnectedEvent args)
{
SetState(component, TwoWayLeverSignal.Middle);
}
private void OnSignalReceived(EntityUid uid, ConveyorComponent component, SignalReceivedEvent args)
{
switch (args.Port)
{
case "state":
SetState(component, (TwoWayLeverSignal) args.Value!);
break;
}
if (Enum.TryParse(args.Port, out ConveyorState state))
SetState(component, state);
}
private void SetState(ConveyorComponent component, TwoWayLeverSignal signal)
private void SetState(ConveyorComponent component, ConveyorState state)
{
component.State = signal switch
{
TwoWayLeverSignal.Left => ConveyorState.Reversed,
TwoWayLeverSignal.Middle => ConveyorState.Off,
TwoWayLeverSignal.Right => ConveyorState.Forward,
_ => ConveyorState.Off
};
component.State = state;
if (TryComp<RecyclerComponent>(component.Owner, out var recycler))
{

View File

@@ -1,9 +0,0 @@
using Robust.Shared.GameObjects;
namespace Content.Server.Doors.Components
{
[RegisterComponent]
public sealed class ToggleDoorOnTriggerComponent : Component
{
}
}

View File

@@ -36,7 +36,6 @@ namespace Content.Server.Explosion.EntitySystems
[Dependency] private readonly ExplosionSystem _explosions = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly FlashSystem _flashSystem = default!;
[Dependency] private readonly DoorSystem _sharedDoorSystem = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
[Dependency] private readonly AdminLogSystem _logSystem = default!;
@@ -52,7 +51,6 @@ namespace Content.Server.Explosion.EntitySystems
SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteTrigger);
SubscribeLocalEvent<ExplodeOnTriggerComponent, TriggerEvent>(HandleExplodeTrigger);
SubscribeLocalEvent<FlashOnTriggerComponent, TriggerEvent>(HandleFlashTrigger);
SubscribeLocalEvent<ToggleDoorOnTriggerComponent, TriggerEvent>(HandleDoorTrigger);
}
private void HandleExplodeTrigger(EntityUid uid, ExplodeOnTriggerComponent component, TriggerEvent args)
@@ -73,11 +71,6 @@ namespace Content.Server.Explosion.EntitySystems
EntityManager.QueueDeleteEntity(uid);
}
private void HandleDoorTrigger(EntityUid uid, ToggleDoorOnTriggerComponent component, TriggerEvent args)
{
_sharedDoorSystem.TryToggleDoor(uid);
}
private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, StartCollideEvent args)
{
Trigger(component.Owner);

View File

@@ -5,6 +5,7 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Ghost;
using Content.Server.Light.Components;
using Content.Server.MachineLinking.Events;
using Content.Server.MachineLinking.Components;
using Content.Server.Power.Components;
using Content.Server.Temperature.Components;
using Content.Shared.Audio;
@@ -58,6 +59,11 @@ namespace Content.Server.Light.EntitySystems
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
{
light.LightBulbContainer = light.Owner.EnsureContainer<ContainerSlot>("light_bulb");
var receiver = EnsureComp<SignalReceiverComponent>(uid);
foreach (string port in new[] { "On", "Off", "Toggle" })
if (!receiver.Inputs.ContainsKey(port))
receiver.AddPort(port);
}
private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args)
@@ -161,7 +167,7 @@ namespace Content.Server.Light.EntitySystems
return null;
// check if light has bulb
if (GetBulb(uid, light) is not {Valid: true} bulb)
if (GetBulb(uid, light) is not { Valid: true } bulb)
return null;
// try to remove bulb from container
@@ -330,16 +336,16 @@ namespace Content.Server.Light.EntitySystems
{
switch (args.Port)
{
case "state":
ToggleLight(uid, component);
break;
case "On": SetState(uid, true, component); break;
case "Off": SetState(uid, false, component); break;
case "Toggle": ToggleLight(uid, component); break;
}
}
/// <summary>
/// Turns the light on or of when receiving a <see cref="DeviceNetworkConstants.CmdSetState"/> command.
/// The light is turned on or of according to the <see cref="DeviceNetworkConstants.StateEnabled"/> value
/// </summary>
/// </summary>
private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, PacketSentEvent args)
{
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return;
@@ -348,7 +354,7 @@ namespace Content.Server.Light.EntitySystems
SetState(uid, enabled, component);
}
private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness=null)
private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null)
{
if (!Resolve(uid, ref light))
return;

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameObjects;
namespace Content.Server.MachineLinking.Components
{
[RegisterComponent]
public sealed class DoorSignalControlComponent : Component
{
}
}

View File

@@ -7,6 +7,9 @@ namespace Content.Server.MachineLinking.Components
public sealed class SignalLinkerComponent : Component
{
[ViewVariables]
public (SignalTransmitterComponent transmitter, string port)? Port;
public EntityUid? savedTransmitter;
[ViewVariables]
public EntityUid? savedReceiver;
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Content.Server.MachineLinking.Models;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@@ -11,9 +10,14 @@ namespace Content.Server.MachineLinking.Components
public sealed class SignalReceiverComponent : Component
{
[DataField("inputs")]
private List<SignalPort> _inputs = new();
private Dictionary<string, List<PortIdentifier>> _inputs = new();
public void AddPort(string name)
{
_inputs.Add(name, new());
}
[ViewVariables]
public IReadOnlyList<SignalPort> Inputs => _inputs;
public IReadOnlyDictionary<string, List<PortIdentifier>> Inputs => _inputs;
}
}

View File

@@ -1,19 +1,48 @@
using System;
using System.Collections.Generic;
using Content.Server.MachineLinking.Models;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using Content.Server.MachineLinking.System;
namespace Content.Server.MachineLinking.Components
{
[DataDefinition]
public struct PortIdentifier
{
[DataField("uid")]
public EntityUid Uid;
[DataField("port")]
public string Port;
public PortIdentifier(EntityUid uid, string port)
{
Uid = uid;
Port = port;
}
}
[RegisterComponent]
public sealed class SignalTransmitterComponent : Component
{
/// <summary>
/// How far the device can transmit a signal wirelessly.
/// Devices farther than this range can still transmit if they are
/// on the same powernet.
/// </summary>
[DataField("transmissionRange")]
public float TransmissionRange = 30f;
[DataField("outputs")]
private List<SignalPort> _outputs = new();
private Dictionary<string, List<PortIdentifier>> _outputs = new();
[ViewVariables]
public IReadOnlyList<SignalPort> Outputs => _outputs;
public IReadOnlyDictionary<string, List<PortIdentifier>> Outputs => _outputs;
public void AddPort(string name)
{
_outputs.Add(name, new());
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Content.Server.MachineLinking.Components
[RegisterComponent]
public sealed class TwoWayLeverComponent : Component
{
public TwoWayLeverSignal State;
public TwoWayLeverState State;
public bool NextSignalLeft;
}

View File

@@ -5,12 +5,10 @@ namespace Content.Server.MachineLinking.Events
public sealed class InvokePortEvent : EntityEventArgs
{
public readonly string Port;
public readonly object? Value;
public InvokePortEvent(string port, object? value = null)
public InvokePortEvent(string port)
{
Port = port;
Value = value;
}
}
}

View File

@@ -5,19 +5,17 @@ namespace Content.Server.MachineLinking.Events
{
public sealed class LinkAttemptEvent : CancellableEntityEventArgs
{
public readonly EntityUid Attemptee;
public readonly SignalTransmitterComponent TransmitterComponent;
public readonly string TransmitterPort;
public readonly SignalReceiverComponent ReceiverComponent;
public readonly string ReceiverPort;
public LinkAttemptEvent(EntityUid attemptee, SignalTransmitterComponent transmitterComponent, string transmitterPort, SignalReceiverComponent receiverComponent, string receiverPort)
public LinkAttemptEvent(SignalTransmitterComponent transmitterComponent, string transmitterPort, SignalReceiverComponent receiverComponent, string receiverPort)
{
TransmitterComponent = transmitterComponent;
this.TransmitterPort = transmitterPort;
ReceiverComponent = receiverComponent;
this.ReceiverPort = receiverPort;
Attemptee = attemptee;
}
}
}

View File

@@ -5,12 +5,10 @@ namespace Content.Server.MachineLinking.Events
public sealed class SignalReceivedEvent : EntityEventArgs
{
public readonly string Port;
public readonly object? Value;
public SignalReceivedEvent(string port, object? value)
public SignalReceivedEvent(string port)
{
Port = port;
Value = value;
}
}
}

View File

@@ -1,19 +0,0 @@
using System;
using Robust.Shared.GameObjects;
namespace Content.Server.MachineLinking.Events
{
public sealed 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

@@ -1,20 +0,0 @@
using Content.Server.MachineLinking.Components;
namespace Content.Server.MachineLinking.Models
{
public sealed 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

@@ -1,131 +0,0 @@
using System.Collections.Generic;
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Exceptions;
namespace Content.Server.MachineLinking.Models
{
public sealed 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

@@ -1,81 +0,0 @@
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 sealed 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

@@ -0,0 +1,41 @@
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Server.Doors.Systems;
using Content.Shared.Doors.Components;
using Content.Shared.Interaction;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.MachineLinking.System
{
[UsedImplicitly]
public sealed class DoorSignalControlSystem : EntitySystem
{
[Dependency] private readonly DoorSystem _doorSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DoorSignalControlComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<DoorSignalControlComponent, SignalReceivedEvent>(OnSignalReceived);
}
private void OnInit(EntityUid uid, DoorSignalControlComponent component, ComponentInit args)
{
var receiver = EnsureComp<SignalReceiverComponent>(uid);
foreach (string port in new[] { "Open", "Close", "Toggle" })
if (!receiver.Inputs.ContainsKey(port))
receiver.AddPort(port);
}
private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, SignalReceivedEvent args)
{
if (!TryComp(uid, out DoorComponent? door)) return;
switch (args.Port)
{
case "Open": if (door.State != DoorState.Open) _doorSystem.TryOpen(uid, door); break;
case "Close": if (door.State != DoorState.Closed) _doorSystem.TryClose(uid, door); break;
case "Toggle": _doorSystem.TryToggleDoor(uid); break;
}
}
}
}

View File

@@ -12,13 +12,20 @@ namespace Content.Server.MachineLinking.System
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SignalButtonComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SignalButtonComponent, InteractHandEvent>(OnInteractHand);
}
private void OnInit(EntityUid uid, SignalButtonComponent component, ComponentInit args)
{
var transmitter = EnsureComp<SignalTransmitterComponent>(uid);
if (!transmitter.Outputs.ContainsKey("Pressed"))
transmitter.AddPort("Pressed");
}
private void OnInteractHand(EntityUid uid, SignalButtonComponent component, InteractHandEvent args)
{
RaiseLocalEvent(uid, new InvokePortEvent("pressed"), false);
RaiseLocalEvent(uid, new InvokePortEvent("Pressed"), false);
args.Handled = true;
}
}

View File

@@ -1,293 +1,338 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Hands.Components;
using Content.Server.Interaction;
using System.Diagnostics.CodeAnalysis;
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.UserInterface;
using Content.Shared.Interaction;
using Content.Shared.MachineLinking;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
using Robust.Shared.Player;
namespace Content.Server.MachineLinking.System
{
public sealed class SignalLinkerSystem : EntitySystem
{
private InteractionSystem _interaction = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
private SignalLinkCollection _linkCollection = new();
private static readonly (string, string)[][] _defaultMappings =
{
new [] { ("Pressed", "Toggle") },
new [] { ("On", "On"), ("Off", "Off") },
new [] { ("On", "Open"), ("Off", "Close") },
new [] { ("On", "Forward"), ("Off", "Off") },
new [] { ("Left", "On"), ("Right", "On"), ("Middle", "Off") },
new [] { ("Left", "Open"), ("Right", "Open"), ("Middle", "Close") },
new [] { ("Left", "Forward"), ("Right", "Reverse"), ("Middle", "Off") },
};
public override void Initialize()
{
base.Initialize();
_interaction = Get<InteractionSystem>();
SubscribeLocalEvent<SignalTransmitterComponent, ComponentStartup>(TransmitterStartupHandler);
SubscribeLocalEvent<SignalTransmitterComponent, InteractUsingEvent>(TransmitterInteractUsingHandler);
SubscribeLocalEvent<SignalTransmitterComponent, InvokePortEvent>(OnTransmitterInvokePort);
SubscribeLocalEvent<SignalTransmitterComponent, ComponentStartup>(OnTransmitterStartup);
SubscribeLocalEvent<SignalTransmitterComponent, ComponentRemove>(OnTransmitterRemoved);
SubscribeLocalEvent<SignalTransmitterComponent, InteractUsingEvent>(OnTransmitterInteractUsing);
SubscribeLocalEvent<SignalReceiverComponent, ComponentStartup>(OnReceiverStartup);
SubscribeLocalEvent<SignalReceiverComponent, ComponentRemove>(OnReceiverRemoved);
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);
SubscribeLocalEvent<SignalLinkerComponent, SignalPortSelected>(OnSignalPortSelected);
SubscribeLocalEvent<SignalLinkerComponent, LinkerClearSelected>(OnLinkerClearSelected);
SubscribeLocalEvent<SignalLinkerComponent, LinkerLinkDefaultSelected>(OnLinkerLinkDefaultSelected);
SubscribeLocalEvent<SignalLinkerComponent, BoundUIClosedEvent>(OnLinkerUIClosed);
}
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,
new SignalReceivedEvent(link.Receiverport.Name, args.Value), false);
}
foreach (var receiver in component.Outputs[args.Port])
RaiseLocalEvent(receiver.Uid, new SignalReceivedEvent(receiver.Port), false);
}
private void OnReceiverInteractUsing(EntityUid uid, SignalReceiverComponent component, InteractUsingEvent args)
private void OnTransmitterStartup(EntityUid uid, SignalTransmitterComponent transmitter, ComponentStartup args)
{
// validate links
Dictionary<EntityUid, SignalReceiverComponent?> uidCache = new();
foreach (var tport in transmitter.Outputs)
foreach (var rport in tport.Value)
{
if (!uidCache.TryGetValue(rport.Uid, out var receiver))
uidCache.Add(rport.Uid, receiver = CompOrNull<SignalReceiverComponent>(rport.Uid));
if (receiver == null || !receiver.Inputs.TryGetValue(rport.Port, out var rpv))
tport.Value.Remove(rport);
else if (!rpv.Contains(new(uid, tport.Key)))
rpv.Add(new(uid, tport.Key));
}
}
private void OnReceiverStartup(EntityUid uid, SignalReceiverComponent receiver, ComponentStartup args)
{
// validate links
Dictionary<EntityUid, SignalTransmitterComponent?> uidCache = new();
foreach (var rport in receiver.Inputs)
foreach (var tport in rport.Value)
{
if (!uidCache.TryGetValue(tport.Uid, out var transmitter))
uidCache.Add(tport.Uid, transmitter = CompOrNull<SignalTransmitterComponent>(tport.Uid));
if (transmitter == null || !transmitter.Outputs.TryGetValue(tport.Port, out var tpv))
rport.Value.Remove(tport);
else if (!tpv.Contains(new(uid, rport.Key)))
tpv.Add(new(uid, rport.Key));
}
}
private void OnTransmitterRemoved(EntityUid uid, SignalTransmitterComponent transmitter, ComponentRemove args)
{
Dictionary<EntityUid, SignalReceiverComponent?> uidCache = new();
foreach (var tport in transmitter.Outputs)
foreach (var rport in tport.Value)
{
if (!uidCache.TryGetValue(rport.Uid, out var receiver))
uidCache.Add(rport.Uid, receiver = CompOrNull<SignalReceiverComponent>(rport.Uid));
if (receiver != null && receiver.Inputs.TryGetValue(rport.Port, out var rpv))
rpv.Remove(new(uid, tport.Key));
}
}
private void OnReceiverRemoved(EntityUid uid, SignalReceiverComponent component, ComponentRemove args)
{
Dictionary<EntityUid, SignalTransmitterComponent?> uidCache = new();
foreach (var rport in component.Inputs)
foreach (var tport in rport.Value)
{
if (!uidCache.TryGetValue(tport.Uid, out var transmitter))
uidCache.Add(tport.Uid, transmitter = CompOrNull<SignalTransmitterComponent>(tport.Uid));
if (transmitter != null && transmitter.Outputs.TryGetValue(tport.Port, out var receivers))
receivers.Remove(new(uid, rport.Key));
}
}
private void OnTransmitterInteractUsing(EntityUid uid, SignalTransmitterComponent transmitter, InteractUsingEvent args)
{
if (args.Handled) return;
if (!EntityManager.TryGetComponent<SignalLinkerComponent?>(args.Used, out var linker) || !linker.Port.HasValue ||
!EntityManager.TryGetComponent(args.User, out ActorComponent? actor) ||
!linker.Port.Value.transmitter.Outputs.TryGetPort(linker.Port.Value.port, out var port))
{
if (!TryComp(args.Used, out SignalLinkerComponent? linker) ||
!TryComp(args.User, out ActorComponent? actor))
return;
}
if (component.Inputs.Count == 1)
linker.savedTransmitter = uid;
if (!TryComp(linker.savedReceiver, out SignalReceiverComponent? receiver))
{
LinkerInteraction(args.User, linker.Port.Value.transmitter, linker.Port.Value.port, component,
component.Inputs[0].Name);
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-saved", ("machine", uid)),
Filter.Entities(args.User));
args.Handled = true;
return;
}
var bui = component.Owner.GetUIOrNull(SignalReceiverUiKey.Key);
if (bui == null)
if (TryGetOrOpenUI(actor, linker, out var bui))
{
TryUpdateUI(linker, transmitter, receiver, bui);
args.Handled = true;
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)
{
if (msg.Session.AttachedEntity is not { } attached) return;
switch (msg.Message)
{
case SignalPortSelected portSelected:
if (msg.Session.AttachedEntity == default ||
!EntityManager.TryGetComponent(msg.Session.AttachedEntity, out HandsComponent? hands) ||
hands.ActiveHandEntity is not EntityUid heldEntity ||
!EntityManager.TryGetComponent(heldEntity, out SignalLinkerComponent? signalLinkerComponent) ||
!signalLinkerComponent.Port.HasValue ||
!signalLinkerComponent.Port.Value.transmitter.Outputs.ContainsPort(signalLinkerComponent.Port
.Value.port) || !component.Inputs.ContainsPort(portSelected.Port))
return;
LinkerInteraction(attached, 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)
{
if (msg.Session.AttachedEntity is not { } attached)
return;
switch (msg.Message)
{
case SignalPortSelected portSelected:
if (msg.Session.AttachedEntity == default ||
!EntityManager.TryGetComponent(msg.Session.AttachedEntity, out HandsComponent? hands) ||
hands.ActiveHandEntity is not EntityUid heldEntity ||
!EntityManager.TryGetComponent(heldEntity, out SignalLinkerComponent? signalLinkerComponent))
return;
LinkerSaveInteraction(attached, signalLinkerComponent, component,
portSelected.Port);
break;
}
}
private void TransmitterInteractUsingHandler(EntityUid uid, SignalTransmitterComponent component,
InteractUsingEvent args)
private void OnReceiverInteractUsing(EntityUid uid, SignalReceiverComponent receiver, InteractUsingEvent args)
{
if (args.Handled) return;
if (!EntityManager.TryGetComponent<SignalLinkerComponent?>(args.Used, out var linker) ||
!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
{
if (!TryComp(args.Used, out SignalLinkerComponent? linker) ||
!TryComp(args.User, out ActorComponent? actor))
return;
}
if (component.Outputs.Count == 1)
linker.savedReceiver = uid;
if (!TryComp(linker.savedTransmitter, out SignalTransmitterComponent? transmitter))
{
var port = component.Outputs.First();
LinkerSaveInteraction(args.User, linker, component, port.Name);
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-saved", ("machine", uid)),
Filter.Entities(args.User));
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(EntityUid entity, SignalTransmitterComponent transmitter, string transmitterPort,
SignalReceiverComponent receiver, string receiverPort)
{
if (_linkCollection.LinkExists(transmitter, transmitterPort, receiver, receiverPort))
if (TryGetOrOpenUI(actor, linker, out var bui))
{
if (_linkCollection.RemoveLink(transmitter, transmitterPort, receiver, receiverPort))
{
RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(receiverPort));
RaiseLocalEvent(transmitter.Owner, 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, linkAttempt);
RaiseLocalEvent(transmitter.Owner, linkAttempt);
if (linkAttempt.Cancelled) return;
var link = _linkCollection.AddLink(transmitter, transmitterPort, receiver, receiverPort);
if (link.Transmitterport.Signal != null)
RaiseLocalEvent(receiver.Owner,
new SignalReceivedEvent(receiverPort, link.Transmitterport.Signal));
entity.PopupMessageCursor(Loc.GetString("signal-linker-component-linked-port", ("port", receiverPort),
("machine", receiver.Owner)));
TryUpdateUI(linker, transmitter, receiver, bui);
args.Handled = true;
return;
}
}
private void LinkerSaveInteraction(EntityUid entity, SignalLinkerComponent linkerComponent,
SignalTransmitterComponent transmitterComponent, string transmitterPort)
private bool TryGetOrOpenUI(ActorComponent actor, SignalLinkerComponent linker, [NotNullWhen(true)] out BoundUserInterface? bui)
{
if (SavePortInSignalLinker(linkerComponent, transmitterComponent, transmitterPort))
if (_userInterfaceSystem.TryGetUi(linker.Owner, SignalLinkerUiKey.Key, out bui))
{
entity.PopupMessageCursor(Loc.GetString("signal-linker-component-saved-port", ("port", transmitterPort),
("machine", transmitterComponent.Owner)));
bui.Open(actor.PlayerSession);
return true;
}
return false;
}
private bool SavePortInSignalLinker(SignalLinkerComponent linker, SignalTransmitterComponent transmitter,
string port)
private bool TryUpdateUI(SignalLinkerComponent linker, SignalTransmitterComponent transmitter, SignalReceiverComponent receiver, BoundUserInterface? bui = null)
{
if (!transmitter.Outputs.ContainsPort(port)) return false;
linker.Port = (transmitter, port);
if (bui == null && !_userInterfaceSystem.TryGetUi(linker.Owner, SignalLinkerUiKey.Key, out bui))
return false;
var outKeys = transmitter.Outputs.Keys.ToList();
var inKeys = receiver.Inputs.Keys.ToList();
List<(int, int)> links = new();
for (int i = 0; i < outKeys.Count; i++)
foreach (var re in transmitter.Outputs[outKeys[i]])
if (re.Uid == receiver.Owner)
links.Add((i, inKeys.IndexOf(re.Port)));
bui.SetState(new SignalPortsState($"{Name(transmitter.Owner)} ({transmitter.Owner})", outKeys,
$"{Name(receiver.Owner)} ({receiver.Owner})", inKeys, links));
return true;
}
private bool TryLink(SignalTransmitterComponent transmitter, SignalReceiverComponent receiver, SignalPortSelected args, EntityUid? popupUid = null)
{
if (!transmitter.Outputs.TryGetValue(args.TransmitterPort, out var receivers) ||
!receiver.Inputs.TryGetValue(args.ReceiverPort, out var transmitters))
return false;
if (!IsInRange(transmitter, receiver))
{
if (popupUid.HasValue)
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-out-of-range"),
Filter.Entities(popupUid.Value));
return false;
}
// allow other systems to refuse the connection
var linkAttempt = new LinkAttemptEvent(transmitter, args.TransmitterPort, receiver, args.ReceiverPort);
RaiseLocalEvent(transmitter.Owner, linkAttempt);
if (linkAttempt.Cancelled)
{
if (popupUid.HasValue)
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-connection-refused", ("machine", transmitter.Owner)),
Filter.Entities(popupUid.Value));
return false;
}
RaiseLocalEvent(receiver.Owner, linkAttempt);
if (linkAttempt.Cancelled)
{
if (popupUid.HasValue)
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-connection-refused", ("machine", receiver.Owner)),
Filter.Entities(popupUid.Value));
return false;
}
receivers.Add(new(receiver.Owner, args.ReceiverPort));
transmitters.Add(new(transmitter.Owner, args.TransmitterPort));
if (popupUid.HasValue)
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-linked-port",
("machine1", transmitter.Owner), ("port1", args.TransmitterPort),
("machine2", receiver.Owner), ("port2", args.ReceiverPort)),
Filter.Entities(popupUid.Value));
return true;
}
private bool IsInRange(SignalTransmitterComponent transmitterComponent,
SignalReceiverComponent receiverComponent)
private void OnSignalPortSelected(EntityUid uid, SignalLinkerComponent linker, SignalPortSelected args)
{
if (EntityManager.TryGetComponent<ApcPowerReceiverComponent?>(transmitterComponent.Owner, out var transmitterPowerReceiverComponent) &&
EntityManager.TryGetComponent<ApcPowerReceiverComponent?>(receiverComponent.Owner, out var receiverPowerReceiverComponent)
) //&& todo are they on the same powernet?
if (!TryComp(linker.savedTransmitter, out SignalTransmitterComponent? transmitter) ||
!TryComp(linker.savedReceiver, out SignalReceiverComponent? receiver) ||
!transmitter.Outputs.TryGetValue(args.TransmitterPort, out var receivers) ||
!receiver.Inputs.TryGetValue(args.ReceiverPort, out var transmitters))
return;
if (args.Session.AttachedEntity is not EntityUid attached || attached == default ||
!TryComp(attached, out ActorComponent? actor))
return;
if (receivers.Contains(new(receiver.Owner, args.ReceiverPort)) ||
transmitters.Contains(new(transmitter.Owner, args.TransmitterPort)))
{ // link already exists, remove it
if (receivers.Remove(new(receiver.Owner, args.ReceiverPort)) &&
transmitters.Remove(new(transmitter.Owner, args.TransmitterPort)))
{
RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(args.ReceiverPort));
RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(args.TransmitterPort));
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-unlinked-port",
("machine1", transmitter.Owner), ("port1", args.TransmitterPort),
("machine2", receiver.Owner), ("port2", args.ReceiverPort)),
Filter.Entities(attached));
}
else
{ // something weird happened
// TODO log error
}
}
else
{
return true;
TryLink(transmitter, receiver, args, attached);
}
return EntityManager.GetComponent<TransformComponent>(transmitterComponent.Owner).MapPosition.InRange(
EntityManager.GetComponent<TransformComponent>(receiverComponent.Owner).MapPosition, 30f);
TryUpdateUI(linker, transmitter, receiver);
}
private void OnLinkerClearSelected(EntityUid uid, SignalLinkerComponent linker, LinkerClearSelected args)
{
if (!TryComp(linker.savedTransmitter, out SignalTransmitterComponent? transmitter) ||
!TryComp(linker.savedReceiver, out SignalReceiverComponent? receiver))
return;
foreach (var (port, receivers) in transmitter.Outputs)
if (receivers.RemoveAll(id => id.Uid == receiver.Owner) > 0)
RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(port));
foreach (var (port, transmitters) in receiver.Inputs)
if (transmitters.RemoveAll(id => id.Uid == transmitter.Owner) > 0)
RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(port));
TryUpdateUI(linker, transmitter, receiver);
}
private void OnLinkerLinkDefaultSelected(EntityUid uid, SignalLinkerComponent linker, LinkerLinkDefaultSelected args)
{
if (!TryComp(linker.savedTransmitter, out SignalTransmitterComponent? transmitter) ||
!TryComp(linker.savedReceiver, out SignalReceiverComponent? receiver) ||
args.Session.AttachedEntity is not EntityUid attached || attached == default ||
!TryComp(attached, out ActorComponent? actor))
return;
if (_defaultMappings.TryFirstOrDefault(map => !map.ExceptBy(transmitter.Outputs.Keys, item => item.Item1).Any() &&
!map.ExceptBy(receiver.Inputs.Keys, item => item.Item2).Any(), out var mapping))
{
foreach (var (port, receivers) in transmitter.Outputs)
if (receivers.RemoveAll(id => id.Uid == receiver.Owner) > 0)
RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(port));
foreach (var (port, transmitters) in receiver.Inputs)
if (transmitters.RemoveAll(id => id.Uid == transmitter.Owner) > 0)
RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(port));
foreach (var (t, r) in mapping)
TryLink(transmitter, receiver, new(t, r));
}
TryUpdateUI(linker, transmitter, receiver);
}
private void OnLinkerUIClosed(EntityUid uid, SignalLinkerComponent component, BoundUIClosedEvent args)
{
component.savedTransmitter = null;
component.savedReceiver = null;
}
private bool IsInRange(SignalTransmitterComponent transmitterComponent, SignalReceiverComponent receiverComponent)
{
if (TryComp(transmitterComponent.Owner, out ApcPowerReceiverComponent? transmitterPower) &&
TryComp(receiverComponent.Owner, out ApcPowerReceiverComponent? receiverPower) &&
transmitterPower.Provider?.Net == receiverPower.Provider?.Net)
return true;
return Comp<TransformComponent>(transmitterComponent.Owner).MapPosition.InRange(
Comp<TransformComponent>(receiverComponent.Owner).MapPosition, transmitterComponent.TransmissionRange);
}
}
}

View File

@@ -10,25 +10,23 @@ namespace Content.Server.MachineLinking.System
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SignalSwitchComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SignalSwitchComponent, InteractHandEvent>(OnInteracted);
SubscribeLocalEvent<SignalSwitchComponent, SignalValueRequestedEvent>(OnSignalValueRequested);
}
private void OnSignalValueRequested(EntityUid uid, SignalSwitchComponent component, SignalValueRequestedEvent args)
private void OnInit(EntityUid uid, SignalSwitchComponent component, ComponentInit args)
{
if (args.Port == "state")
{
args.Handled = true;
args.Signal = component.State;
}
var transmitter = EnsureComp<SignalTransmitterComponent>(uid);
foreach (string port in new[] { "On", "Off" })
if (!transmitter.Outputs.ContainsKey(port))
transmitter.AddPort(port);
}
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);
RaiseLocalEvent(uid, new InvokePortEvent(component.State ? "On" : "Off"), false);
args.Handled = true;
}
}

View File

@@ -4,7 +4,6 @@ using Content.Server.MachineLinking.Events;
using Content.Shared.Interaction;
using Content.Shared.MachineLinking;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.MachineLinking.System
{
@@ -13,28 +12,29 @@ namespace Content.Server.MachineLinking.System
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TwoWayLeverComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<TwoWayLeverComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<TwoWayLeverComponent, SignalValueRequestedEvent>(OnSignalValueRequested);
}
private void OnSignalValueRequested(EntityUid uid, TwoWayLeverComponent component, SignalValueRequestedEvent args)
private void OnInit(EntityUid uid, TwoWayLeverComponent component, ComponentInit args)
{
args.Signal = component.State;
args.Handled = true;
var transmitter = EnsureComp<SignalTransmitterComponent>(uid);
foreach (string state in Enum.GetNames<TwoWayLeverState>())
if (!transmitter.Outputs.ContainsKey(state))
transmitter.AddPort(state);
}
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,
TwoWayLeverState.Middle => component.NextSignalLeft ? TwoWayLeverState.Left : TwoWayLeverState.Right,
TwoWayLeverState.Right => TwoWayLeverState.Middle,
TwoWayLeverState.Left => TwoWayLeverState.Middle,
_ => throw new ArgumentOutOfRangeException()
};
if (component.State == TwoWayLeverSignal.Middle)
if (component.State == TwoWayLeverState.Middle)
{
component.NextSignalLeft = !component.NextSignalLeft;
}
@@ -44,7 +44,7 @@ namespace Content.Server.MachineLinking.System
appearanceComponent.SetData(TwoWayLeverVisuals.State, component.State);
}
RaiseLocalEvent(uid, new InvokePortEvent("state", component.State));
RaiseLocalEvent(uid, new InvokePortEvent(component.State.ToString()));
args.Handled = true;
}
}

View File

@@ -51,7 +51,7 @@ namespace Content.Server.Physics.Controllers
conveyorRot += comp.Angle;
if (comp.State == ConveyorState.Reversed)
if (comp.State == ConveyorState.Reverse)
{
conveyorRot += MathF.PI;
}

View File

@@ -14,6 +14,6 @@ namespace Content.Shared.Conveyor
{
Off,
Forward,
Reversed
Reverse
}
}

View File

@@ -10,7 +10,7 @@ namespace Content.Shared.MachineLinking
}
[Serializable, NetSerializable]
public enum TwoWayLeverSignal : byte
public enum TwoWayLeverState : byte
{
Middle,
Right,

View File

@@ -4,13 +4,7 @@ using Robust.Shared.Serialization;
namespace Content.Shared.MachineLinking
{
[Serializable, NetSerializable]
public enum SignalTransmitterUiKey
{
Key
}
[Serializable, NetSerializable]
public enum SignalReceiverUiKey
public enum SignalLinkerUiKey
{
Key
}

View File

@@ -9,29 +9,46 @@ namespace Content.Shared.MachineLinking
[Serializable, NetSerializable]
public sealed class SignalPortsState : BoundUserInterfaceState
{
public readonly string TransmitterName;
/// <summary>
/// A Dictionary containing all ports and wether or not they can be selected.
/// A List of all ports on the selected transmitter
/// </summary>
public readonly Dictionary<string, bool> Ports;
public readonly List<string> TransmitterPorts;
public SignalPortsState(string[] ports) : this(ports.ToDictionary(s => s, _ => true))
{
}
public readonly string ReceiverName;
/// <summary>
/// A List of all ports on the selected receiver
/// </summary>
public readonly List<string> ReceiverPorts;
public SignalPortsState(Dictionary<string, bool> ports)
public readonly List<(int, int)> Links;
public SignalPortsState(string transmitterName, List<string> transmitterPorts, string receiverName, List<string> receiverPorts, List<(int, int)> links)
{
Ports = ports;
TransmitterName = transmitterName;
TransmitterPorts = transmitterPorts;
ReceiverName = receiverName;
ReceiverPorts = receiverPorts;
Links = links;
}
}
[Serializable, NetSerializable]
public sealed class SignalPortSelected : BoundUserInterfaceMessage
{
public readonly string Port;
public readonly string TransmitterPort;
public readonly string ReceiverPort;
public SignalPortSelected(string port)
public SignalPortSelected(string transmitterPort, string receiverPort)
{
Port = port;
TransmitterPort = transmitterPort;
ReceiverPort = receiverPort;
}
}
[Serializable, NetSerializable]
public sealed class LinkerClearSelected : BoundUserInterfaceMessage { }
[Serializable, NetSerializable]
public sealed class LinkerLinkDefaultSelected : BoundUserInterfaceMessage { }
}

View File

@@ -1,7 +1,7 @@
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-saved = Successfully saved link to {$machine}!
signal-linker-component-linked-port = Successfully linked {$machine1}:{$port1} to {$machine2}:{$port2}!
signal-linker-component-unlinked-port = Successfully unlinked {$machine1}:{$port1} from {$machine2}:{$port2}!
signal-linker-component-connection-refused = {$machine} refused the connection!
signal-linker-component-max-connections-receiver = Maximum connections reached on the receiver!
signal-linker-component-max-connections-transmitter = Maximum connections reached on the transmitter!

View File

@@ -181,6 +181,10 @@
qualities:
- Pulsing
- type: SignalLinker
- type: UserInterface
interfaces:
- key: enum.SignalLinkerUiKey.Key
type: SignalPortSelectorBoundUserInterface
- type: Tag
tags:
- DroneUsable

View File

@@ -66,11 +66,7 @@
- type: IconSmooth
key: walls
mode: NoSprite
- type: SignalReceiver
inputs:
- name: pressed
- type: ToggleDoorOnTrigger
- type: TriggerOnSignalReceived
- type: DoorSignalControl
placement:
mode: SnapgridCenter

View File

@@ -49,8 +49,3 @@
state_off: grinder-o0
- type: Recycler
- type: Conveyor
- type: SignalReceiver
inputs:
- name: state
type: Content.Shared.MachineLinking.TwoWayLeverSignal
maxConnections: 1

View File

@@ -32,14 +32,6 @@
energy: 0.8
softness: 1
offset: "0, -0.5"
- type: SignalReceiver
inputs:
- name: state
type: bool
- type: UserInterface
interfaces:
- key: enum.SignalReceiverUiKey.Key
type: SignalPortSelectorBoundUserInterface
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
@@ -132,7 +124,7 @@
energy: 2.5
softness: 0.9
color: "#EEEEFF"
- type: entity
id: PoweredlightExterior
description: "A light fixture. Draws power and produces light when equipped with a light tube."
@@ -155,7 +147,7 @@
energy: 4.5
softness: 0.5
color: "#B4FCF0"
- type: entity
id: PoweredlightSodium

View File

@@ -13,15 +13,6 @@
state: on
- type: SignalSwitch
- type: Rotatable
- type: SignalTransmitter
outputs:
- name: state
type: bool
- name: stateChange
- type: UserInterface
interfaces:
- key: enum.SignalTransmitterUiKey.Key
type: SignalPortSelectorBoundUserInterface
placement:
mode: SnapgridCenter
snap:
@@ -42,9 +33,6 @@
state: dead
- type: SignalButton
- type: Rotatable
- type: SignalTransmitter
outputs:
- name: pressed
placement:
mode: SnapgridCenter
snap:
@@ -78,3 +66,36 @@
mode: SnapgridCenter
snap:
- Wallmount
- type: entity
id: TwoWayLever
name: two way lever
description: A two way lever.
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
sprite: Structures/conveyor.rsi
state: switch-off
- type: TwoWayLever
- type: Appearance
visuals:
- type: TwoWayLeverVisualizer
state_forward: switch-fwd
state_off: switch-off
state_reversed: switch-rev
- type: Construction
graph: LeverGraph
node: lever
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 100
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]

View File

@@ -16,11 +16,6 @@
sprite: Structures/conveyor.rsi
state: conveyor_started_cw
drawdepth: FloorObjects
- type: SignalReceiver
inputs:
- name: state
type: Content.Shared.MachineLinking.TwoWayLeverSignal
maxConnections: 1
- type: ApcPowerReceiver
- type: ExtensionCableReceiver
- type: Conveyor
@@ -61,38 +56,3 @@
graph: ConveyorGraph
node: item
- type: entity
id: TwoWayLever
name: two way lever
description: A two way lever.
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
sprite: Structures/conveyor.rsi
state: switch-off
- type: TwoWayLever
- type: SignalTransmitter
outputs:
- name: state
type: Content.Shared.MachineLinking.TwoWayLeverSignal
- type: Appearance
visuals:
- type: TwoWayLeverVisualizer
state_forward: switch-fwd
state_off: switch-off
state_reversed: switch-rev
- type: Construction
graph: LeverGraph
node: lever
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 100
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]

View File

@@ -33,14 +33,6 @@
- !type:PlaySoundBehavior
sound:
path: /Audio/Effects/metalbreak.ogg
- type: SignalReceiver
inputs:
- name: state
type: bool
- type: UserInterface
interfaces:
- key: enum.SignalReceiverUiKey.Key
type: SignalPortSelectorBoundUserInterface
- type: entity