From: deltanedas <39013340+deltanedas@users.noreply.github.com>
Date: Sat, 16 Dec 2023 18:32:42 +0000 (+0000)
Subject: add power sensor (#20400)
X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=d8ee36d7b0bac053f285847fc30d860f38b39679;p=space-station-14.git
add power sensor (#20400)
* clean up logic gate / edge detector components
* logic gate usedelay support
* new codersprite
* PowerSensor component and system
* add power sensor
* port locale
* fix
* minecraft
* fixy
---------
Co-authored-by: deltanedas <@deltanedas:kde.org>
---
diff --git a/Content.Server/DeviceLinking/Components/EdgeDetectorComponent.cs b/Content.Server/DeviceLinking/Components/EdgeDetectorComponent.cs
index 603aa2aa20..0a59f98468 100644
--- a/Content.Server/DeviceLinking/Components/EdgeDetectorComponent.cs
+++ b/Content.Server/DeviceLinking/Components/EdgeDetectorComponent.cs
@@ -1,35 +1,34 @@
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;
///
/// An edge detector that pulses high or low output ports when the input port gets a rising or falling edge respectively.
///
-[RegisterComponent]
-[Access(typeof(EdgeDetectorSystem))]
+[RegisterComponent, Access(typeof(EdgeDetectorSystem))]
public sealed partial class EdgeDetectorComponent : Component
{
///
/// Name of the input port.
///
- [DataField("inputPort", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string InputPort = "Input";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId InputPort = "Input";
///
/// Name of the rising edge output port.
///
- [DataField("outputHighPort", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string OutputHighPort = "OutputHigh";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId OutputHighPort = "OutputHigh";
///
/// Name of the falling edge output port.
///
- [DataField("outputLowPort", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string OutputLowPort = "OutputLow";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId OutputLowPort = "OutputLow";
// Initial state
- [ViewVariables]
+ [DataField]
public SignalState State = SignalState.Low;
}
diff --git a/Content.Server/DeviceLinking/Components/LogicGateComponent.cs b/Content.Server/DeviceLinking/Components/LogicGateComponent.cs
index ee055c5df8..61f85934b4 100644
--- a/Content.Server/DeviceLinking/Components/LogicGateComponent.cs
+++ b/Content.Server/DeviceLinking/Components/LogicGateComponent.cs
@@ -2,62 +2,61 @@ using Content.Server.DeviceLinking.Systems;
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;
///
/// A logic gate that sets its output port by doing an operation on its 2 input ports, A and B.
///
-[RegisterComponent]
-[Access(typeof(LogicGateSystem))]
+[RegisterComponent, Access(typeof(LogicGateSystem))]
public sealed partial class LogicGateComponent : Component
{
///
/// The logic gate operation to use.
///
- [DataField("gate")]
+ [DataField]
public LogicGate Gate = LogicGate.Or;
///
/// Tool quality to use for cycling logic gate operations.
/// Cannot be pulsing since linking uses that.
///
- [DataField("cycleQuality", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string CycleQuality = "Screwing";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId CycleQuality = "Screwing";
///
/// Sound played when cycling logic gate operations.
///
- [DataField("cycleSound")]
+ [DataField]
public SoundSpecifier CycleSound = new SoundPathSpecifier("/Audio/Machines/lightswitch.ogg");
///
/// Name of the first input port.
///
- [DataField("inputPortA", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string InputPortA = "InputA";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId InputPortA = "InputA";
///
/// Name of the second input port.
///
- [DataField("inputPortB", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string InputPortB = "InputB";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId InputPortB = "InputB";
///
/// Name of the output port.
///
- [DataField("outputPort", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string OutputPort = "Output";
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId OutputPort = "Output";
// Initial state
- [ViewVariables]
+ [DataField]
public SignalState StateA = SignalState.Low;
- [ViewVariables]
+ [DataField]
public SignalState StateB = SignalState.Low;
- [ViewVariables]
+ [DataField]
public bool LastOutput;
}
diff --git a/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs b/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs
new file mode 100644
index 0000000000..d9599546ae
--- /dev/null
+++ b/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs
@@ -0,0 +1,76 @@
+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;
+
+///
+/// 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 to function.
+///
+[RegisterComponent, Access(typeof(PowerSensorSystem))]
+public sealed partial class PowerSensorComponent : Component
+{
+ ///
+ /// 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.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool Output;
+
+ ///
+ /// Tool quality to use for switching between input and output.
+ /// Cannot be pulsing since linking uses that.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId SwitchQuality = "Screwing";
+
+ ///
+ /// Sound played when switching between input and output.
+ ///
+ [DataField]
+ public SoundSpecifier SwitchSound = new SoundPathSpecifier("/Audio/Machines/lightswitch.ogg");
+
+ ///
+ /// Name of the port set when the network is charging power.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId ChargingPort = "PowerCharging";
+
+ ///
+ /// Name of the port set when the network is discharging power.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId DischargingPort = "PowerDischarging";
+
+ ///
+ /// How long to wait before checking the power network.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan CheckDelay = TimeSpan.FromSeconds(1);
+
+ ///
+ /// Time at which power will be checked.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextCheck = TimeSpan.Zero;
+
+ ///
+ /// Charge the network was at, at the last check.
+ /// Charging/discharging is derived from this.
+ ///
+ [DataField]
+ public float LastCharge;
+
+ // Initial state
+ [DataField]
+ public bool ChargingState;
+
+ [DataField]
+ public bool DischargingState;
+}
diff --git a/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs b/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs
index 60d13b78fc..115285413f 100644
--- a/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs
+++ b/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs
@@ -3,8 +3,9 @@ using Content.Server.DeviceNetwork;
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;
@@ -19,6 +20,7 @@ public sealed class LogicGateSystem : EntitySystem
[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;
@@ -71,6 +73,10 @@ public sealed class LogicGateSystem : EntitySystem
if (args.Handled || !_tool.HasQuality(args.Used, comp.CycleQuality))
return;
+ // no sound spamming
+ if (TryComp(uid, out var useDelay) && _useDelay.ActiveDelay(uid, useDelay))
+ return;
+
// cycle through possible gates
var gate = (int) comp.Gate;
gate = ++gate % GateCount;
@@ -84,6 +90,8 @@ public sealed class LogicGateSystem : EntitySystem
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)
diff --git a/Content.Server/DeviceLinking/Systems/PowerSensorSystem.cs b/Content.Server/DeviceLinking/Systems/PowerSensorSystem.cs
new file mode 100644
index 0000000000..cabcabe1ae
--- /dev/null
+++ b/Content.Server/DeviceLinking/Systems/PowerSensorSystem.cs
@@ -0,0 +1,140 @@
+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 _nodeQuery;
+ private EntityQuery _xformQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _nodeQuery = GetEntityQuery();
+ _xformQuery = GetEntityQuery();
+
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnExamined);
+ SubscribeLocalEvent(OnInteractUsing);
+ }
+
+ public override void Update(float deltaTime)
+ {
+ var query = EntityQueryEnumerator();
+ 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(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(uid);
+ var cable = powerSwitchable.Cables[powerSwitchable.ActiveIndex];
+ var nodeContainer = Comp(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);
+ }
+ }
+}
diff --git a/Resources/Locale/en-US/logic-gates/logic-gates.ftl b/Resources/Locale/en-US/logic-gates/logic-gates.ftl
index 1195670e7f..17346aae6e 100644
--- a/Resources/Locale/en-US/logic-gates/logic-gates.ftl
+++ b/Resources/Locale/en-US/logic-gates/logic-gates.ftl
@@ -1,3 +1,15 @@
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}!
diff --git a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
index e5f92c5b00..c685cc8fb7 100644
--- a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
+++ b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
@@ -59,4 +59,10 @@ signal-port-name-pulse = Pulse
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.
diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml
index 6e97c3c809..18b9b831e6 100644
--- a/Resources/Prototypes/DeviceLinking/source_ports.yml
+++ b/Resources/Prototypes/DeviceLinking/source_ports.yml
@@ -129,13 +129,23 @@
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
diff --git a/Resources/Prototypes/Entities/Structures/gates.yml b/Resources/Prototypes/Entities/Structures/gates.yml
index 8e5b1595c8..8fccb39b71 100644
--- a/Resources/Prototypes/Entities/Structures/gates.yml
+++ b/Resources/Prototypes/Entities/Structures/gates.yml
@@ -25,6 +25,8 @@
- state: or
map: [ "enum.LogicGateLayers.Gate" ]
- type: LogicGate
+ - type: UseDelay
+ delay: 0.5
- type: DeviceLinkSink
ports:
- InputA
@@ -66,3 +68,46 @@
- 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
diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml b/Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml
index cf620eaaca..6e64f061eb 100644
--- a/Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml
+++ b/Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml
@@ -20,7 +20,22 @@
- 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
diff --git a/Resources/Prototypes/Recipes/Construction/tools.yml b/Resources/Prototypes/Recipes/Construction/tools.yml
index cfa968b6ec..a6d9e33f4c 100644
--- a/Resources/Prototypes/Recipes/Construction/tools.yml
+++ b/Resources/Prototypes/Recipes/Construction/tools.yml
@@ -30,3 +30,14 @@
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
diff --git a/Resources/Textures/Objects/Devices/gates.rsi/meta.json b/Resources/Textures/Objects/Devices/gates.rsi/meta.json
index 1a4ca51419..9d10f53279 100644
--- a/Resources/Textures/Objects/Devices/gates.rsi/meta.json
+++ b/Resources/Textures/Objects/Devices/gates.rsi/meta.json
@@ -33,6 +33,9 @@
},
{
"name": "or_icon"
+ },
+ {
+ "name": "power_sensor"
}
]
}
diff --git a/Resources/Textures/Objects/Devices/gates.rsi/power_sensor.png b/Resources/Textures/Objects/Devices/gates.rsi/power_sensor.png
new file mode 100644
index 0000000000..3bd6de6d10
Binary files /dev/null and b/Resources/Textures/Objects/Devices/gates.rsi/power_sensor.png differ