Blast door/shutter, timer and or gate device linking fixes (#16347)

This commit is contained in:
Julian Giebel
2023-05-12 00:16:02 +02:00
committed by GitHub
parent 59176df425
commit 5e0a96dfc7
16 changed files with 255 additions and 123 deletions

View File

@@ -0,0 +1,15 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.DeviceLinking.Components
{
[RegisterComponent]
public sealed class ActiveSignalTimerComponent : Component
{
/// <summary>
/// The time the timer triggers.
/// </summary>
[DataField("triggerTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan TriggerTime;
}
}

View File

@@ -0,0 +1,34 @@
using Content.Server.MachineLinking.Events;
namespace Content.Server.DeviceLinking.Components;
[RegisterComponent]
public sealed class OrGateComponent : Component
{
// Initial state
[ViewVariables]
public SignalState StateA1 = SignalState.Low;
[ViewVariables]
public SignalState StateB1 = SignalState.Low;
[ViewVariables]
public SignalState LastO1 = SignalState.Low;
[ViewVariables]
public SignalState StateA2 = SignalState.Low;
[ViewVariables]
public SignalState StateB2 = SignalState.Low;
[ViewVariables]
public SignalState LastO2 = SignalState.Low;
}
public enum SignalState
{
Momentary, // Instantaneous pulse high, compatibility behavior
Low,
High
}

View File

@@ -0,0 +1,42 @@
using Content.Shared.MachineLinking;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.DeviceLinking.Components;
[RegisterComponent]
public sealed class SignalTimerComponent : Component
{
[DataField("delay"), ViewVariables(VVAccess.ReadWrite)]
public double Delay = 5;
/// <summary>
/// This shows the Label: text box in the UI.
/// </summary>
[DataField("canEditLabel"), ViewVariables(VVAccess.ReadWrite)]
public bool CanEditLabel = true;
/// <summary>
/// The label, used for TextScreen visuals currently.
/// </summary>
[DataField("label"), ViewVariables(VVAccess.ReadWrite)]
public string Label = "";
/// <summary>
/// The port that gets signaled when the timer triggers.
/// </summary>
[DataField("triggerPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string TriggerPort = "Timer";
/// <summary>
/// The port that gets signaled when the timer starts.
/// </summary>
[DataField("startPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string StartPort = "Start";
/// <summary>
/// If not null, this timer will play this sound when done.
/// </summary>
[DataField("doneSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier? DoneSound;
}

View File

@@ -1,8 +1,6 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Server.Doors.Systems;
using Content.Server.MachineLinking.Events;
using Content.Server.MachineLinking.System;
using Content.Shared.Doors.Components;
using Content.Shared.Doors;
using JetBrains.Annotations;
@@ -17,8 +15,6 @@ namespace Content.Server.DeviceLinking.Systems
[Dependency] private readonly DoorSystem _doorSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
private const string DoorSignalState = "DoorState";
public override void Initialize()
{
base.Initialize();
@@ -40,7 +36,7 @@ namespace Content.Server.DeviceLinking.Systems
return;
var state = SignalState.Momentary;
args.Data?.TryGetValue(DoorSignalState, out state);
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
if (args.Port == component.OpenPort)
@@ -85,12 +81,12 @@ namespace Content.Server.DeviceLinking.Systems
{
var data = new NetworkPayload()
{
{ DoorSignalState, SignalState.Momentary }
{ DeviceNetworkConstants.LogicState, SignalState.Momentary }
};
if (args.State == DoorState.Closed)
{
data[DoorSignalState] = SignalState.Low;
data[DeviceNetworkConstants.LogicState] = SignalState.Low;
_signalSystem.InvokePort(uid, door.OutOpen, data);
}
else if (args.State == DoorState.Open
@@ -98,7 +94,7 @@ namespace Content.Server.DeviceLinking.Systems
|| args.State == DoorState.Closing
|| args.State == DoorState.Emagging)
{
data[DoorSignalState] = SignalState.High;
data[DeviceNetworkConstants.LogicState] = SignalState.High;
_signalSystem.InvokePort(uid, door.OutOpen, data);
}
}

View File

@@ -0,0 +1,84 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Server.MachineLinking.Events;
using JetBrains.Annotations;
using Robust.Shared.Utility;
using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent;
namespace Content.Server.DeviceLinking.Systems
{
[UsedImplicitly]
public sealed class OrGateSystem : EntitySystem
{
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OrGateComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<OrGateComponent, SignalReceivedEvent>(OnSignalReceived);
}
private void OnInit(EntityUid uid, OrGateComponent component, ComponentInit args)
{
_signalSystem.EnsureSinkPorts(uid, "A1", "B1", "A2", "B2");
_signalSystem.EnsureSourcePorts(uid, "O1", "O2");
}
private void OnSignalReceived(EntityUid uid, OrGateComponent component, ref SignalReceivedEvent args)
{
var state = SignalState.Momentary;
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
switch (args.Port)
{
case "A1":
component.StateA1 = state;
break;
case "B1":
component.StateB1 = state;
break;
case "A2":
component.StateA2 = state;
break;
case "B2":
component.StateB2 = state;
break;
}
// O1 = A1 || B1
var v1 = SignalState.Low;
if (component.StateA1 == SignalState.High || component.StateB1 == SignalState.High)
v1 = SignalState.High;
if (v1 != component.LastO1)
{
var data = new NetworkPayload
{
[DeviceNetworkConstants.LogicState] = v1
};
_signalSystem.InvokePort(uid, "O1", data);
}
component.LastO1 = v1;
// O2 = A2 || B2
var v2 = SignalState.Low;
if (component.StateA2 == SignalState.High || component.StateB2 == SignalState.High)
v2 = SignalState.High;
if (v2 != component.LastO2)
{
var data = new NetworkPayload
{
[DeviceNetworkConstants.LogicState] = v2
};
_signalSystem.InvokePort(uid, "O2", data);
}
component.LastO2 = v2;
}
}
}

View File

@@ -0,0 +1,162 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.Interaction;
using Content.Server.UserInterface;
using Content.Shared.Access.Systems;
using Content.Shared.MachineLinking;
using Content.Shared.TextScreen;
using Robust.Server.GameObjects;
using Robust.Shared.Timing;
namespace Content.Server.DeviceLinking.Systems;
public sealed class SignalTimerSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly InteractionSystem _interaction = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SignalTimerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SignalTimerComponent, AfterActivatableUIOpenEvent>(OnAfterActivatableUIOpen);
SubscribeLocalEvent<SignalTimerComponent, SignalTimerTextChangedMessage>(OnTextChangedMessage);
SubscribeLocalEvent<SignalTimerComponent, SignalTimerDelayChangedMessage>(OnDelayChangedMessage);
SubscribeLocalEvent<SignalTimerComponent, SignalTimerStartMessage>(OnTimerStartMessage);
}
private void OnInit(EntityUid uid, SignalTimerComponent component, ComponentInit args)
{
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
}
private void OnAfterActivatableUIOpen(EntityUid uid, SignalTimerComponent component, AfterActivatableUIOpenEvent args)
{
var time = TryComp<ActiveSignalTimerComponent>(uid, out var active) ? active.TriggerTime : TimeSpan.Zero;
if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui))
{
_ui.SetUiState(bui, new SignalTimerBoundUserInterfaceState(component.Label,
TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"),
TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"),
component.CanEditLabel,
time,
active != null,
_accessReader.IsAllowed(args.User, uid)));
}
}
public void Trigger(EntityUid uid, SignalTimerComponent signalTimer)
{
RemComp<ActiveSignalTimerComponent>(uid);
_signalSystem.InvokePort(uid, signalTimer.TriggerPort);
_appearanceSystem.SetData(uid, TextScreenVisuals.Mode, TextScreenMode.Text);
if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui))
{
_ui.SetUiState(bui, new SignalTimerBoundUserInterfaceState(signalTimer.Label,
TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"),
TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"),
signalTimer.CanEditLabel,
TimeSpan.Zero,
false,
true));
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateTimer();
}
private void UpdateTimer()
{
var query = EntityQueryEnumerator<ActiveSignalTimerComponent, SignalTimerComponent>();
while (query.MoveNext(out var uid, out var active, out var timer))
{
if (active.TriggerTime > _gameTiming.CurTime)
continue;
Trigger(uid, timer);
if (timer.DoneSound == null)
continue;
_audio.PlayPvs(timer.DoneSound, uid);
}
}
/// <summary>
/// Checks if a UI <paramref name="message"/> is allowed to be sent by the user.
/// </summary>
/// <param name="uid">The entity that is interacted with.</param>
/// <param name="message"></param>
private bool IsMessageValid(EntityUid uid, BoundUserInterfaceMessage message)
{
if (message.Session.AttachedEntity is not { Valid: true } mob)
return false;
if (!_accessReader.IsAllowed(mob, uid))
return false;
return true;
}
private void OnTextChangedMessage(EntityUid uid, SignalTimerComponent component, SignalTimerTextChangedMessage args)
{
if (!IsMessageValid(uid, args))
return;
component.Label = args.Text[..Math.Min(5,args.Text.Length)];
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
}
private void OnDelayChangedMessage(EntityUid uid, SignalTimerComponent component, SignalTimerDelayChangedMessage args)
{
if (!IsMessageValid(uid, args))
return;
component.Delay = args.Delay.TotalSeconds;
}
private void OnTimerStartMessage(EntityUid uid, SignalTimerComponent component, SignalTimerStartMessage args)
{
if (!IsMessageValid(uid, args))
return;
TryComp<AppearanceComponent>(uid, out var appearance);
if (!HasComp<ActiveSignalTimerComponent>(uid))
{
var activeTimer = EnsureComp<ActiveSignalTimerComponent>(uid);
activeTimer.TriggerTime = _gameTiming.CurTime + TimeSpan.FromSeconds(component.Delay);
if (appearance != null)
{
_appearanceSystem.SetData(uid, TextScreenVisuals.Mode, TextScreenMode.Timer, appearance);
_appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, activeTimer.TriggerTime, appearance);
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label, appearance);
}
_signalSystem.InvokePort(uid, component.StartPort);
}
else
{
RemComp<ActiveSignalTimerComponent>(uid);
if (appearance != null)
{
_appearanceSystem.SetData(uid, TextScreenVisuals.Mode, TextScreenMode.Text, appearance);
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label, appearance);
}
}
}
}