]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
add power sensor (#20400)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Sat, 16 Dec 2023 18:32:42 +0000 (18:32 +0000)
committerGitHub <noreply@github.com>
Sat, 16 Dec 2023 18:32:42 +0000 (11:32 -0700)
* 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>
13 files changed:
Content.Server/DeviceLinking/Components/EdgeDetectorComponent.cs
Content.Server/DeviceLinking/Components/LogicGateComponent.cs
Content.Server/DeviceLinking/Components/PowerSensorComponent.cs [new file with mode: 0644]
Content.Server/DeviceLinking/Systems/LogicGateSystem.cs
Content.Server/DeviceLinking/Systems/PowerSensorSystem.cs [new file with mode: 0644]
Resources/Locale/en-US/logic-gates/logic-gates.ftl
Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
Resources/Prototypes/DeviceLinking/source_ports.yml
Resources/Prototypes/Entities/Structures/gates.yml
Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml
Resources/Prototypes/Recipes/Construction/tools.yml
Resources/Textures/Objects/Devices/gates.rsi/meta.json
Resources/Textures/Objects/Devices/gates.rsi/power_sensor.png [new file with mode: 0644]

index 603aa2aa20eb72996b750b0ad29c39a8a45edd4c..0a59f98468182cbdcb5e821d35fb63635ed38ba9 100644 (file)
@@ -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;
 
 /// <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;
 }
index ee055c5df8277b79b7687574bf49d7fa57282198..61f85934b401ca3e781354c2c83d0bb4cacdb350 100644 (file)
@@ -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;
 
 /// <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;
 }
 
diff --git a/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs b/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs
new file mode 100644 (file)
index 0000000..d959954
--- /dev/null
@@ -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;
+
+/// <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;
+}
index 60d13b78fc214d3d62c82f64c48664d3213a87f9..115285413f396af7b8556fef159aaa3624cad991 100644 (file)
@@ -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<UseDelayComponent>(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 (file)
index 0000000..cabcabe
--- /dev/null
@@ -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<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);
+        }
+    }
+}
index 1195670e7f41b48296707513a3146b048e42311e..17346aae6e86af006598318e7c98779e69ecd659 100644 (file)
@@ -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}!
index e5f92c5b0015197a53468c93cbb971dab83141df..c685cc8fb78de17a3ea8fb0613cf9e6671c2cb54 100644 (file)
@@ -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.
index 6e97c3c809c62f89f5d6e6e677b8caf98cf3c3bf..18b9b831e6b83468834e072aba0e5da106e82732 100644 (file)
   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
index 8e5b1595c88036cd9395dad44876230388607392..8fccb39b71c83de69543e43ad596982621244188 100644 (file)
@@ -25,6 +25,8 @@
     - 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
index cf620eaacaf85fdf61dc4464a0bc392fc29d78c9..6e64f061eb9838cd8e61caba0139ac54d8c068c4 100644 (file)
       - 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
index cfa968b6ec7e7474abf4f513b5addce5214d6e75..a6d9e33f4c10c2f48f988790f7b5a3724a6fd5ce 100644 (file)
   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
index 1a4ca51419e091b743ab1870b7bbdcb70e163914..9d10f53279ad504b8cf43dc13a0bf35c3695fef6 100644 (file)
@@ -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 (file)
index 0000000..3bd6de6
Binary files /dev/null and b/Resources/Textures/Objects/Devices/gates.rsi/power_sensor.png differ