Machine Port Prototypes (#7659)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Leon Friedrich
2022-05-12 20:46:20 +12:00
committed by GitHub
parent 32c2eb7a02
commit c00b459e31
29 changed files with 422 additions and 220 deletions

View File

@@ -1,9 +1,18 @@
using Robust.Shared.GameObjects;
using Content.Shared.MachineLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.MachineLinking.Components
{
[RegisterComponent]
public sealed class DoorSignalControlComponent : Component
{
[DataField("openPort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
public string OpenPort = "Open";
[DataField("closePort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
public string ClosePort = "Close";
[DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
public string TogglePort = "Toggle";
}
}

View File

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

View File

@@ -1,23 +1,12 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using Content.Server.MachineLinking.System;
namespace Content.Server.MachineLinking.Components
{
[RegisterComponent]
[Friend(typeof(SignalLinkerSystem))]
public sealed class SignalReceiverComponent : Component
{
[DataField("inputs")]
private Dictionary<string, List<PortIdentifier>> _inputs = new();
public void AddPort(string name)
{
_inputs.Add(name, new());
}
[ViewVariables]
public IReadOnlyDictionary<string, List<PortIdentifier>> Inputs => _inputs;
public Dictionary<string, List<PortIdentifier>> Inputs = new();
}
}

View File

@@ -1,10 +1,28 @@
using Robust.Shared.GameObjects;
using Content.Shared.MachineLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.MachineLinking.Components
{
/// <summary>
/// Simple switch that will fire ports when toggled on or off. A button is jsut a switch that signals on the
/// same port regardless of its state.
/// </summary>
[RegisterComponent]
public sealed class SignalSwitchComponent : Component
{
/// <summary>
/// The port that gets signaled when the switch turns on.
/// </summary>
[DataField("onPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
public string OnPort = "On";
/// <summary>
/// The port that gets signaled when the switch turns off.
/// </summary>
[DataField("offPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
public string OffPort = "Off";
[DataField("state")]
public bool State;
}
}

View File

@@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using Content.Server.MachineLinking.System;
namespace Content.Server.MachineLinking.Components
@@ -24,6 +19,7 @@ namespace Content.Server.MachineLinking.Components
}
[RegisterComponent]
[Friend(typeof(SignalLinkerSystem))]
public sealed class SignalTransmitterComponent : Component
{
/// <summary>
@@ -35,14 +31,6 @@ namespace Content.Server.MachineLinking.Components
public float TransmissionRange = 30f;
[DataField("outputs")]
private Dictionary<string, List<PortIdentifier>> _outputs = new();
[ViewVariables]
public IReadOnlyDictionary<string, List<PortIdentifier>> Outputs => _outputs;
public void AddPort(string name)
{
_outputs.Add(name, new());
}
public Dictionary<string, List<PortIdentifier>> Outputs = new();
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameObjects;
using Content.Shared.MachineLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.MachineLinking.Components
{
@@ -8,6 +9,10 @@ namespace Content.Server.MachineLinking.Components
[RegisterComponent]
public sealed class SignallerComponent : Component
{
public const string Port = "Pressed";
/// <summary>
/// The port that gets signaled when the switch turns on.
/// </summary>
[DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
public string Port = "Pressed";
}
}

View File

@@ -1,13 +1,24 @@
using Content.Shared.MachineLinking;
using Robust.Shared.GameObjects;
using Content.Shared.MachineLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.MachineLinking.Components
{
[RegisterComponent]
public sealed class TwoWayLeverComponent : Component
{
[DataField("state")]
public TwoWayLeverState State;
[DataField("nextSignalLeft")]
public bool NextSignalLeft;
[DataField("leftPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
public string LeftPort = "Left";
[DataField("rightPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
public string RightPort = "Right";
[DataField("middlePort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
public string MiddlePort = "Middle";
}
}

View File

@@ -1,14 +0,0 @@
using Robust.Shared.GameObjects;
namespace Content.Server.MachineLinking.Events
{
public sealed class InvokePortEvent : EntityEventArgs
{
public readonly string Port;
public InvokePortEvent(string port)
{
Port = port;
}
}
}

View File

@@ -2,9 +2,7 @@ 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
{
@@ -12,6 +10,8 @@ namespace Content.Server.MachineLinking.System
public sealed class DoorSignalControlSystem : EntitySystem
{
[Dependency] private readonly DoorSystem _doorSystem = default!;
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -21,20 +21,27 @@ namespace Content.Server.MachineLinking.System
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);
_signalSystem.EnsureReceiverPorts(uid, component.OpenPort, component.ClosePort, component.TogglePort);
}
private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, SignalReceivedEvent args)
{
if (!TryComp(uid, out DoorComponent? door)) return;
switch (args.Port)
if (!TryComp(uid, out DoorComponent? door))
return;
if (args.Port == component.OpenPort)
{
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;
if (door.State != DoorState.Open)
_doorSystem.TryOpen(uid, door);
}
else if (args.Port == component.ClosePort)
{
if (door.State != DoorState.Closed)
_doorSystem.TryClose(uid, door);
}
else if (args.Port == component.TogglePort)
{
_doorSystem.TryToggleDoor(uid, door);
}
}
}

View File

@@ -1,33 +0,0 @@
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Shared.Interaction;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Content.Shared.Interaction.Events;
namespace Content.Server.MachineLinking.System
{
[UsedImplicitly]
public sealed class SignalButtonSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SignalButtonComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SignalButtonComponent, ActivateInWorldEvent>(OnActivated);
}
private void OnInit(EntityUid uid, SignalButtonComponent component, ComponentInit args)
{
var transmitter = EnsureComp<SignalTransmitterComponent>(uid);
if (!transmitter.Outputs.ContainsKey("Pressed"))
transmitter.AddPort("Pressed");
}
private void OnActivated(EntityUid uid, SignalButtonComponent component, ActivateInWorldEvent args)
{
RaiseLocalEvent(uid, new InvokePortEvent("Pressed"), false);
args.Handled = true;
}
}
}

View File

@@ -7,9 +7,9 @@ using Content.Shared.Interaction;
using Content.Shared.MachineLinking;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
using Robust.Shared.Utility;
using Robust.Shared.Player;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
using Content.Shared.MachineLinking.Events;
namespace Content.Server.MachineLinking.System
@@ -18,24 +18,12 @@ namespace Content.Server.MachineLinking.System
{
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
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") },
};
[Dependency] private readonly IPrototypeManager _protoMan = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SignalTransmitterComponent, InvokePortEvent>(OnTransmitterInvokePort);
SubscribeLocalEvent<SignalTransmitterComponent, ComponentStartup>(OnTransmitterStartup);
SubscribeLocalEvent<SignalTransmitterComponent, ComponentRemove>(OnTransmitterRemoved);
SubscribeLocalEvent<SignalTransmitterComponent, InteractUsingEvent>(OnTransmitterInteractUsing);
@@ -52,6 +40,27 @@ namespace Content.Server.MachineLinking.System
SubscribeLocalEvent<SignalLinkerComponent, BoundUIClosedEvent>(OnLinkerUIClosed);
}
/// <summary>
/// Convenience function to add several ports to an entity.
/// </summary>
public void EnsureReceiverPorts(EntityUid uid, params string[] ports)
{
var comp = EnsureComp<SignalReceiverComponent>(uid);
foreach (var port in ports)
{
comp.Inputs.TryAdd(port, new());
}
}
public void EnsureTransmitterPorts(EntityUid uid, params string[] ports)
{
var comp = EnsureComp<SignalTransmitterComponent>(uid);
foreach (var port in ports)
{
comp.Outputs.TryAdd(port, new());
}
}
/// <summary>
/// Add an alt-click verb to allow users to link the default ports, without needing to open the UI.
/// </summary>
@@ -117,9 +126,15 @@ namespace Content.Server.MachineLinking.System
verb.Message = Loc.GetString("signal-linking-verb-disabled-no-receiver");
}
private void OnTransmitterInvokePort(EntityUid uid, SignalTransmitterComponent component, InvokePortEvent args)
public void InvokePort(EntityUid uid, string port, SignalTransmitterComponent? component = null)
{
foreach (var receiver in component.Outputs[args.Port])
if (!Resolve(uid, ref component))
return;
if (!component.Outputs.TryGetValue(port, out var receivers))
return;
foreach (var receiver in receivers)
RaiseLocalEvent(receiver.Uid, new SignalReceivedEvent(receiver.Port), false);
}
@@ -264,10 +279,18 @@ namespace Content.Server.MachineLinking.System
private bool TryLink(SignalTransmitterComponent transmitter, SignalReceiverComponent receiver, SignalPortSelected args, EntityUid user, bool quiet = false, bool checkRange = true)
{
if (!transmitter.Outputs.TryGetValue(args.TransmitterPort, out var receivers) ||
!receiver.Inputs.TryGetValue(args.ReceiverPort, out var transmitters))
if (!transmitter.Outputs.TryGetValue(args.TransmitterPort, out var linkedReceivers) ||
!receiver.Inputs.TryGetValue(args.ReceiverPort, out var linkedTransmitters))
return false;
// Does the link already exist? Under the assumption that nothing has broken, lets only check the
// transmitter ports.
foreach (var identifier in linkedTransmitters)
{
if (identifier.Uid == transmitter.Owner && identifier.Port == args.TransmitterPort)
return true;
}
if (checkRange && !IsInRange(transmitter, receiver))
{
if (!quiet)
@@ -295,12 +318,12 @@ namespace Content.Server.MachineLinking.System
return false;
}
receivers.Add(new(receiver.Owner, args.ReceiverPort));
transmitters.Add(new(transmitter.Owner, args.TransmitterPort));
linkedReceivers.Add(new(receiver.Owner, args.ReceiverPort));
linkedTransmitters.Add(new(transmitter.Owner, args.TransmitterPort));
if (!quiet)
_popupSystem.PopupCursor(Loc.GetString("signal-linker-component-linked-port",
("machine1", transmitter.Owner), ("port1", args.TransmitterPort),
("machine2", receiver.Owner), ("port2", args.ReceiverPort)),
("machine1", transmitter.Owner), ("port1", PortName<TransmitterPortPrototype>(args.TransmitterPort)),
("machine2", receiver.Owner), ("port2", PortName<ReceiverPortPrototype>(args.ReceiverPort))),
Filter.Entities(user));
return true;
@@ -326,8 +349,8 @@ namespace Content.Server.MachineLinking.System
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)),
("machine1", transmitter.Owner), ("port1", PortName<TransmitterPortPrototype>(args.TransmitterPort)),
("machine2", receiver.Owner), ("port2", PortName<ReceiverPortPrototype>(args.ReceiverPort))),
Filter.Entities(attached));
}
else
@@ -343,6 +366,19 @@ namespace Content.Server.MachineLinking.System
TryUpdateUI(linker, transmitter, receiver);
}
/// <summary>
/// Convenience function to retrieve the name of a port prototype.
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public string PortName<TPort>(string port) where TPort : MachinePortPrototype, IPrototype
{
if (!_protoMan.TryIndex<TPort>(port, out var proto))
return port;
return Loc.GetString(proto.Name);
}
private void OnLinkerClearSelected(EntityUid uid, SignalLinkerComponent linker, LinkerClearSelected args)
{
if (!TryComp(linker.SavedTransmitter, out SignalTransmitterComponent? transmitter) ||
@@ -388,19 +424,27 @@ namespace Content.Server.MachineLinking.System
var allLinksSucceeded = true;
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))
// First, disconnect existing links.
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));
// Then make any valid default connections.
foreach (var outPort in transmitter.Outputs.Keys)
{
foreach (var (port, receivers) in transmitter.Outputs)
if (receivers.RemoveAll(id => id.Uid == receiver.Owner) > 0)
RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(port));
var prototype = _protoMan.Index<TransmitterPortPrototype>(outPort);
if (prototype.DefaultLinks == null)
continue;
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)
allLinksSucceeded &= !TryLink(transmitter, receiver, new(t, r), user, quiet: true, checkRange: false);
foreach (var inPort in prototype.DefaultLinks)
{
if (receiver.Inputs.ContainsKey(inPort))
allLinksSucceeded &= TryLink(transmitter, receiver, new(outPort, inPort), user, quiet: true, checkRange: false);
}
}
return allLinksSucceeded;

View File

@@ -1,32 +1,33 @@
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Shared.Interaction;
using Robust.Shared.GameObjects;
namespace Content.Server.MachineLinking.System
{
public sealed class SignalSwitchSystem : EntitySystem
{
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SignalSwitchComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SignalSwitchComponent, ActivateInWorldEvent>(OnActivated);
}
private void OnInit(EntityUid uid, SignalSwitchComponent component, ComponentInit args)
{
var transmitter = EnsureComp<SignalTransmitterComponent>(uid);
foreach (string port in new[] { "On", "Off" })
if (!transmitter.Outputs.ContainsKey(port))
transmitter.AddPort(port);
_signalSystem.EnsureTransmitterPorts(uid, component.OnPort, component.OffPort);
}
private void OnActivated(EntityUid uid, SignalSwitchComponent component, ActivateInWorldEvent args)
{
if (args.Handled)
return;
component.State = !component.State;
RaiseLocalEvent(uid, new InvokePortEvent(component.State ? "On" : "Off"), false);
_signalSystem.InvokePort(uid, component.State ? component.OnPort : component.OffPort);
args.Handled = true;
}
}

View File

@@ -1,8 +1,5 @@
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Shared.Interaction;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Content.Shared.Interaction.Events;
namespace Content.Server.MachineLinking.System
@@ -10,6 +7,7 @@ namespace Content.Server.MachineLinking.System
[UsedImplicitly]
public sealed class SignallerSystem : EntitySystem
{
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -19,17 +17,15 @@ namespace Content.Server.MachineLinking.System
private void OnInit(EntityUid uid, SignallerComponent component, ComponentInit args)
{
var transmitter = EnsureComp<SignalTransmitterComponent>(uid);
if (!transmitter.Outputs.ContainsKey(SignallerComponent.Port))
transmitter.AddPort(SignallerComponent.Port);
_signalSystem.EnsureTransmitterPorts(uid, component.Port);
}
private void OnUseInHand(EntityUid uid, SignallerComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
RaiseLocalEvent(uid, new InvokePortEvent(SignallerComponent.Port), false);
_signalSystem.InvokePort(uid, component.Port);
args.Handled = true;
}
}
}
}

View File

@@ -1,14 +1,14 @@
using System;
using Content.Server.MachineLinking.Components;
using Content.Server.MachineLinking.Events;
using Content.Shared.Interaction;
using Content.Shared.MachineLinking;
using Robust.Shared.GameObjects;
namespace Content.Server.MachineLinking.System
{
public sealed class TwoWayLeverSystem : EntitySystem
{
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -18,14 +18,14 @@ namespace Content.Server.MachineLinking.System
private void OnInit(EntityUid uid, TwoWayLeverComponent component, ComponentInit args)
{
var transmitter = EnsureComp<SignalTransmitterComponent>(uid);
foreach (string state in Enum.GetNames<TwoWayLeverState>())
if (!transmitter.Outputs.ContainsKey(state))
transmitter.AddPort(state);
_signalSystem.EnsureTransmitterPorts(uid, component.LeftPort, component.RightPort, component.MiddlePort);
}
private void OnActivated(EntityUid uid, TwoWayLeverComponent component, ActivateInWorldEvent args)
{
if (args.Handled)
return;
component.State = component.State switch
{
TwoWayLeverState.Middle => component.NextSignalLeft ? TwoWayLeverState.Left : TwoWayLeverState.Right,
@@ -35,16 +35,20 @@ namespace Content.Server.MachineLinking.System
};
if (component.State == TwoWayLeverState.Middle)
{
component.NextSignalLeft = !component.NextSignalLeft;
}
if (EntityManager.TryGetComponent<AppearanceComponent>(uid, out var appearanceComponent))
{
if (TryComp(uid, out AppearanceComponent? appearanceComponent))
appearanceComponent.SetData(TwoWayLeverVisuals.State, component.State);
}
RaiseLocalEvent(uid, new InvokePortEvent(component.State.ToString()));
var port = component.State switch
{
TwoWayLeverState.Left => component.LeftPort,
TwoWayLeverState.Right => component.RightPort,
TwoWayLeverState.Middle => component.MiddlePort,
_ => throw new ArgumentOutOfRangeException()
};
_signalSystem.InvokePort(uid, port);
args.Handled = true;
}
}