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