using Content.Server.DeviceLinking.Systems;
using Content.Shared.DeviceLinking;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
namespace Content.Server.DeviceLinking.Components;
/// <summary>
/// An edge detector that pulses high or low output ports when the input port gets a rising or falling edge respectively.
/// </summary>
-[RegisterComponent]
-[Access(typeof(EdgeDetectorSystem))]
+[RegisterComponent, Access(typeof(EdgeDetectorSystem))]
public sealed partial class EdgeDetectorComponent : Component
{
/// <summary>
/// Name of the input port.
/// </summary>
- [DataField("inputPort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
- public string InputPort = "Input";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<SinkPortPrototype> InputPort = "Input";
/// <summary>
/// Name of the rising edge output port.
/// </summary>
- [DataField("outputHighPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
- public string OutputHighPort = "OutputHigh";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<SourcePortPrototype> OutputHighPort = "OutputHigh";
/// <summary>
/// Name of the falling edge output port.
/// </summary>
- [DataField("outputLowPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
- public string OutputLowPort = "OutputLow";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<SourcePortPrototype> OutputLowPort = "OutputLow";
// Initial state
- [ViewVariables]
+ [DataField]
public SignalState State = SignalState.Low;
}
using Content.Shared.DeviceLinking;
using Content.Shared.Tools;
using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
namespace Content.Server.DeviceLinking.Components;
/// <summary>
/// A logic gate that sets its output port by doing an operation on its 2 input ports, A and B.
/// </summary>
-[RegisterComponent]
-[Access(typeof(LogicGateSystem))]
+[RegisterComponent, Access(typeof(LogicGateSystem))]
public sealed partial class LogicGateComponent : Component
{
/// <summary>
/// The logic gate operation to use.
/// </summary>
- [DataField("gate")]
+ [DataField]
public LogicGate Gate = LogicGate.Or;
/// <summary>
/// Tool quality to use for cycling logic gate operations.
/// Cannot be pulsing since linking uses that.
/// </summary>
- [DataField("cycleQuality", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
- public string CycleQuality = "Screwing";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<ToolQualityPrototype> CycleQuality = "Screwing";
/// <summary>
/// Sound played when cycling logic gate operations.
/// </summary>
- [DataField("cycleSound")]
+ [DataField]
public SoundSpecifier CycleSound = new SoundPathSpecifier("/Audio/Machines/lightswitch.ogg");
/// <summary>
/// Name of the first input port.
/// </summary>
- [DataField("inputPortA", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
- public string InputPortA = "InputA";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<SinkPortPrototype> InputPortA = "InputA";
/// <summary>
/// Name of the second input port.
/// </summary>
- [DataField("inputPortB", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
- public string InputPortB = "InputB";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<SinkPortPrototype> InputPortB = "InputB";
/// <summary>
/// Name of the output port.
/// </summary>
- [DataField("outputPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
- public string OutputPort = "Output";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<SourcePortPrototype> OutputPort = "Output";
// Initial state
- [ViewVariables]
+ [DataField]
public SignalState StateA = SignalState.Low;
- [ViewVariables]
+ [DataField]
public SignalState StateB = SignalState.Low;
- [ViewVariables]
+ [DataField]
public bool LastOutput;
}
--- /dev/null
+using Content.Server.DeviceLinking.Systems;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Tools;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.DeviceLinking.Components;
+
+/// <summary>
+/// A power sensor checks the power network it's anchored to.
+/// Has 2 ports for when it is charging or discharging. They should never both be high.
+/// Requires <see cref="PowerSwitchableComponent"/> to function.
+/// </summary>
+[RegisterComponent, Access(typeof(PowerSensorSystem))]
+public sealed partial class PowerSensorComponent : Component
+{
+ /// <summary>
+ /// Whether to check the power network's input or output battery stats.
+ /// Useful when working with SMESes where input and output can both be important.
+ /// Or with APCs where there is no output and only input.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool Output;
+
+ /// <summary>
+ /// Tool quality to use for switching between input and output.
+ /// Cannot be pulsing since linking uses that.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<ToolQualityPrototype> SwitchQuality = "Screwing";
+
+ /// <summary>
+ /// Sound played when switching between input and output.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier SwitchSound = new SoundPathSpecifier("/Audio/Machines/lightswitch.ogg");
+
+ /// <summary>
+ /// Name of the port set when the network is charging power.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<SourcePortPrototype> ChargingPort = "PowerCharging";
+
+ /// <summary>
+ /// Name of the port set when the network is discharging power.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<SourcePortPrototype> DischargingPort = "PowerDischarging";
+
+ /// <summary>
+ /// How long to wait before checking the power network.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan CheckDelay = TimeSpan.FromSeconds(1);
+
+ /// <summary>
+ /// Time at which power will be checked.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextCheck = TimeSpan.Zero;
+
+ /// <summary>
+ /// Charge the network was at, at the last check.
+ /// Charging/discharging is derived from this.
+ /// </summary>
+ [DataField]
+ public float LastCharge;
+
+ // Initial state
+ [DataField]
+ public bool ChargingState;
+
+ [DataField]
+ public bool DischargingState;
+}
using Content.Shared.DeviceLinking;
using Content.Shared.Examine;
using Content.Shared.Interaction;
-using Content.Shared.Tools;
using Content.Shared.Popups;
+using Content.Shared.Timing;
+using Content.Shared.Tools;
using Content.Shared.Tools.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedToolSystem _tool = default!;
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
private readonly int GateCount = Enum.GetValues(typeof(LogicGate)).Length;
if (args.Handled || !_tool.HasQuality(args.Used, comp.CycleQuality))
return;
+ // no sound spamming
+ if (TryComp<UseDelayComponent>(uid, out var useDelay) && _useDelay.ActiveDelay(uid, useDelay))
+ return;
+
// cycle through possible gates
var gate = (int) comp.Gate;
gate = ++gate % GateCount;
var msg = Loc.GetString("logic-gate-cycle", ("gate", comp.Gate.ToString().ToUpper()));
_popup.PopupEntity(msg, uid, args.User);
_appearance.SetData(uid, LogicGateVisuals.Gate, comp.Gate);
+
+ _useDelay.BeginDelay(uid, useDelay);
}
private void OnSignalReceived(EntityUid uid, LogicGateComponent comp, ref SignalReceivedEvent args)
--- /dev/null
+using Content.Server.DeviceLinking.Components;
+using Content.Server.DeviceNetwork;
+using Content.Server.NodeContainer;
+using Content.Server.Power.EntitySystems;
+using Content.Server.Power.Nodes;
+using Content.Server.Power.NodeGroups;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Content.Shared.Popups;
+using Content.Shared.Power.Generator;
+using Content.Shared.Timing;
+using Content.Shared.Tools;
+using Content.Shared.Tools.Systems;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Map;
+using Robust.Shared.Timing;
+
+namespace Content.Server.DeviceLinking.Systems;
+
+public sealed class PowerSensorSystem : EntitySystem
+{
+ [Dependency] private readonly DeviceLinkSystem _deviceLink = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly PowerNetSystem _powerNet = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedToolSystem _tool = default!;
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
+
+ private EntityQuery<NodeContainerComponent> _nodeQuery;
+ private EntityQuery<TransformComponent> _xformQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _nodeQuery = GetEntityQuery<NodeContainerComponent>();
+ _xformQuery = GetEntityQuery<TransformComponent>();
+
+ SubscribeLocalEvent<PowerSensorComponent, ComponentInit>(OnInit);
+ SubscribeLocalEvent<PowerSensorComponent, ExaminedEvent>(OnExamined);
+ SubscribeLocalEvent<PowerSensorComponent, InteractUsingEvent>(OnInteractUsing);
+ }
+
+ public override void Update(float deltaTime)
+ {
+ var query = EntityQueryEnumerator<PowerSensorComponent>();
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ var now = _timing.CurTime;
+ if (comp.NextCheck > now)
+ continue;
+
+ comp.NextCheck = now + comp.CheckDelay;
+ UpdateOutputs(uid, comp);
+ }
+ }
+
+ private void OnInit(EntityUid uid, PowerSensorComponent comp, ComponentInit args)
+ {
+ _deviceLink.EnsureSourcePorts(uid, comp.ChargingPort, comp.DischargingPort);
+ }
+
+ private void OnExamined(EntityUid uid, PowerSensorComponent comp, ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+
+ args.PushMarkup(Loc.GetString("power-sensor-examine", ("output", comp.Output)));
+ }
+
+ private void OnInteractUsing(EntityUid uid, PowerSensorComponent comp, InteractUsingEvent args)
+ {
+ if (args.Handled || !_tool.HasQuality(args.Used, comp.SwitchQuality))
+ return;
+
+ // no sound spamming
+ if (TryComp<UseDelayComponent>(uid, out var useDelay) && _useDelay.ActiveDelay(uid, useDelay))
+ return;
+
+ // switch between input and output mode.
+ comp.Output = !comp.Output;
+
+ // since the battery to be checked changed the output probably has too, update it
+ UpdateOutputs(uid, comp);
+
+ // notify the user
+ _audio.PlayPvs(comp.SwitchSound, uid);
+ var msg = Loc.GetString("power-sensor-switch", ("output", comp.Output));
+ _popup.PopupEntity(msg, uid, args.User);
+
+ _useDelay.BeginDelay(uid, useDelay);
+ }
+
+ private void UpdateOutputs(EntityUid uid, PowerSensorComponent comp)
+ {
+ // get power stats on the power network that's been switched to
+ var powerSwitchable = Comp<PowerSwitchableComponent>(uid);
+ var cable = powerSwitchable.Cables[powerSwitchable.ActiveIndex];
+ var nodeContainer = Comp<NodeContainerComponent>(uid);
+ var deviceNode = (CableDeviceNode) nodeContainer.Nodes[cable.Node];
+
+ var charge = 0f;
+ var chargingState = false;
+ var dischargingState = false;
+
+ // update state based on the power stats retrieved from the selected power network
+ var xform = _xformQuery.GetComponent(uid);
+ _mapManager.TryGetGrid(xform.GridUid, out var grid);
+ var cables = deviceNode.GetReachableNodes(xform, _nodeQuery, _xformQuery, grid, EntityManager);
+ foreach (var node in cables)
+ {
+ if (node.NodeGroup == null)
+ continue;
+
+ var group = (IBasePowerNet) node.NodeGroup;
+ var stats = _powerNet.GetNetworkStatistics(group.NetworkNode);
+ charge = comp.Output ? stats.OutStorageCurrent : stats.InStorageCurrent;
+ chargingState = charge > comp.LastCharge;
+ dischargingState = charge < comp.LastCharge;
+ break;
+ }
+
+ comp.LastCharge = charge;
+
+ // send new signals if changed
+ if (comp.ChargingState != chargingState)
+ {
+ comp.ChargingState = chargingState;
+ _deviceLink.SendSignal(uid, comp.ChargingPort, chargingState);
+ }
+
+ if (comp.DischargingState != dischargingState)
+ {
+ comp.DischargingState = dischargingState;
+ _deviceLink.SendSignal(uid, comp.DischargingPort, dischargingState);
+ }
+ }
+}
logic-gate-examine = It is currently {INDEFINITE($gate)} {$gate} gate.
logic-gate-cycle = Switched to {INDEFINITE($gate)} {$gate} gate
+
+power-sensor-examine = It is currently checking the network's {$output ->
+ [true] output
+ *[false] input
+} battery.
+power-sensor-voltage-examine = It is checking the {$voltage} power network.
+
+power-sensor-switch = Switched to checking the network's {$output ->
+ [true] output
+ *[false] input
+} battery.
+power-sensor-voltage-switch = Switched network to {$voltage}!
signal-port-description-pulse = This port is invoked when a bound anomaly is pulsing.
signal-port-name-supercrit = Supercritical
-signal-port-description-supercrit = This port is invoked when a bound anomaly explode after supercrit state.
\ No newline at end of file
+signal-port-description-supercrit = This port is invoked when a bound anomaly explode after supercrit state.
+
+signal-port-name-power-charging = Charging
+signal-port-description-power-charging = This port is invoked with HIGH when the battery is gaining charge and LOW when not.
+
+signal-port-name-power-discharging = Discharging
+signal-port-description-power-discharging = This port is invoked with HIGH when the battery is losing charge and LOW when not.
id: Growing
name: signal-port-name-growing
description: signal-port-description-growing
-
+
- type: sourcePort
id: Pulse
name: signal-port-name-pulse
description: signal-port-description-pulse
-
+
- type: sourcePort
id: Supercritical
name: signal-port-name-supercrit
- description: signal-port-description-supercrit
\ No newline at end of file
+ description: signal-port-description-supercrit
+
+- type: sourcePort
+ id: PowerCharging
+ name: signal-port-name-power-charging
+ description: signal-port-description-power-charging
+
+- type: sourcePort
+ id: PowerDischarging
+ name: signal-port-name-power-discharging
+ description: signal-port-description-power-discharging
- state: or
map: [ "enum.LogicGateLayers.Gate" ]
- type: LogicGate
+ - type: UseDelay
+ delay: 0.5
- type: DeviceLinkSink
ports:
- InputA
- type: Construction
graph: LogicGate
node: edge_detector
+
+- type: entity
+ parent: BaseLogicItem
+ id: PowerSensor
+ name: power sensor
+ description: Generates signals in response to powernet changes. Can be cycled between cable voltages.
+ components:
+ - type: Sprite
+ state: power_sensor
+ - type: PowerSensor
+ - type: PowerSwitchable
+ examineText: power-sensor-voltage-examine
+ switchText: power-sensor-voltage-switch
+ cables:
+ - voltage: HV
+ node: hv
+ - voltage: MV
+ node: mv
+ - voltage: LV
+ node: lv
+ - type: UseDelay
+ delay: 1
+ - type: NodeContainer
+ examinable: true
+ nodes:
+ hv:
+ !type:CableDeviceNode
+ nodeGroupID: HVPower
+ mv:
+ !type:CableDeviceNode
+ nodeGroupID: MVPower
+ enabled: false
+ lv:
+ !type:CableDeviceNode
+ nodeGroupID: Apc
+ enabled: false
+ - type: DeviceLinkSource
+ ports:
+ - PowerCharging
+ - PowerDischarging
+ - type: Construction
+ graph: LogicGate
+ node: power_sensor
- material: Cable
amount: 2
doAfter: 1
+ - to: power_sensor
+ steps:
+ - material: Steel
+ amount: 3
+ doAfter: 1
+ - material: Cable
+ amount: 2
+ doAfter: 1
+ - tag: Multitool
+ icon:
+ sprite: Objects/Tools/multitool.rsi
+ state: icon
+ name: a multitool
- node: logic_gate
entity: LogicGate
- node: edge_detector
entity: EdgeDetector
+ - node: power_sensor
+ entity: PowerSensor
description: An edge detector for signals.
icon: { sprite: Objects/Devices/gates.rsi, state: edge_detector }
objectType: Item
+
+- type: construction
+ name: power sensor
+ id: PowerSensor
+ graph: LogicGate
+ startNode: start
+ targetNode: power_sensor
+ category: construction-category-tools
+ description: A power network checking device for signals.
+ icon: { sprite: Objects/Devices/gates.rsi, state: power_sensor }
+ objectType: Item
},
{
"name": "or_icon"
+ },
+ {
+ "name": "power_sensor"
}
]
}