From 5227489360126521b5ded181dc63ee29f73c6d87 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Sat, 4 Oct 2025 13:24:42 +0200 Subject: [PATCH] Predict EMPs (#39802) * predicted emps * fixes * fix * review --- Content.Client/Emp/EmpSystem.cs | 14 +- .../Power/EntitySystems/BatterySystem.cs | 5 + .../Power/EntitySystems/ChargerSystem.cs | 5 + .../SurveillanceCameraSystem.cs | 5 + .../VendingMachines/VendingMachineSystem.cs | 1 + .../Tests/Power/PowerTest.cs | 3 +- .../Tests/Power/StationPowerTests.cs | 1 + .../Systems/AdminVerbSystem.Tools.cs | 1 + .../Systems/ChameleonClothingSystem.cs | 34 +--- .../Construction/Completions/BuildMech.cs | 2 +- Content.Server/Emp/EmpSystem.cs | 94 ----------- Content.Server/Holosign/HolosignSystem.cs | 3 +- .../EntitySystems/EmergencyLightSystem.cs | 1 + .../Light/EntitySystems/PoweredLightSystem.cs | 9 -- Content.Server/Mech/Systems/MechSystem.cs | 2 +- .../Medical/SuitSensors/SuitSensorSystem.cs | 28 ---- .../Ninja/Systems/BatteryDrainerSystem.cs | 2 +- .../Ninja/Systems/NinjaSuitSystem.cs | 22 +-- .../Ninja/Systems/SpaceNinjaSystem.cs | 8 +- .../Components/BatteryChargerComponent.cs | 1 + .../Power/Components/BatteryComponent.cs | 67 -------- .../Power/Components/ChargerComponent.cs | 37 ----- .../Power/EntitySystems/ApcSystem.cs | 6 +- .../EntitySystems/BatteryInterfaceSystem.cs | 1 + .../Power/EntitySystems/BatterySystem.cs | 31 ++-- .../Power/EntitySystems/ChargerSystem.cs | 21 +-- .../PowerMonitoringConsoleSystem.cs | 3 +- .../Power/EntitySystems/RiggableSystem.cs | 1 + Content.Server/Power/SMES/SmesSystem.cs | 1 + .../Power/SetBatteryPercentCommand.cs | 2 +- .../PowerCell/PowerCellSystem.Draw.cs | 2 +- Content.Server/PowerCell/PowerCellSystem.cs | 25 +-- Content.Server/PowerSink/PowerSinkSystem.cs | 14 +- .../Radio/EntitySystems/HeadsetSystem.cs | 16 +- .../BatterySensorComponent.cs | 1 + .../SensorMonitoring/BatterySensorSystem.cs | 4 +- .../Silicons/StationAi/StationAiSystem.cs | 1 + .../Stunnable/Systems/StunbatonSystem.cs | 2 + .../SurveillanceCameraMicrophoneSystem.cs | 1 + .../Systems/SurveillanceCameraSystem.cs | 52 +----- .../Tesla/EntitySystem/TeslaCoilSystem.cs | 2 +- .../VendingMachines/VendingMachineSystem.cs | 15 +- .../Ranged/Systems/GunSystem.Battery.cs | 3 +- .../XAE/Components/XAEEmpInAreaComponent.cs | 2 +- .../Artifact/XAE/XAEChargeBatterySystem.cs | 2 +- .../Components/ElectricityAnomalyComponent.cs | 2 +- .../SharedChameleonClothingSystem.cs | 50 +++++- Content.Shared/Emp/EmpDisabledComponent.cs | 24 +-- Content.Shared/Emp/SharedEmpSystem.cs | 148 +++++++++++++++++- .../Effects/EmpReactionEffect.cs | 2 +- .../EntitySystems/SharedPoweredLightSystem.cs | 31 ++-- .../SuitSensors/SharedSuitSensorSystem.cs | 22 +++ .../SuitSensors/SuitSensorComponent.cs | 4 +- .../Ninja/Components/NinjaSuitComponent.cs | 4 +- .../Ninja/Systems/SharedNinjaSuitSystem.cs | 14 +- Content.Shared/Power/ChargeEvents.cs | 33 ++++ .../ApcPowerReceiverBatteryComponent.cs | 2 +- .../Power/Components/BatteryComponent.cs | 34 ++++ .../Power/Components/ChargerComponent.cs | 36 +++++ .../EntitySystems/SharedBatterySystem.cs | 44 ++++++ .../EntitySystems/SharedChargerSystem.cs | 20 +++ .../PowerCell/SharedPowerCellSystem.cs | 11 ++ .../Radio/Components/HeadsetComponent.cs | 10 +- .../EntitySystems/SharedHeadsetSystem.cs | 16 +- .../Components/SurveillanceCameraComponent.cs | 24 ++- .../SharedSurveillanceCameraSystem.cs | 50 +++++- .../Trigger/Systems/EmpOnTriggerSystem.cs | 3 +- .../SharedVendingMachineSystem.cs | 18 ++- .../VendingMachineComponent.cs | 3 + .../Entities/Effects/emp_effects.yml | 23 ++- 70 files changed, 667 insertions(+), 514 deletions(-) create mode 100644 Content.Client/Power/EntitySystems/BatterySystem.cs create mode 100644 Content.Client/Power/EntitySystems/ChargerSystem.cs create mode 100644 Content.Client/SurveillanceCamera/SurveillanceCameraSystem.cs delete mode 100644 Content.Server/Power/Components/BatteryComponent.cs delete mode 100644 Content.Server/Power/Components/ChargerComponent.cs create mode 100644 Content.Shared/Power/ChargeEvents.cs create mode 100644 Content.Shared/Power/Components/BatteryComponent.cs create mode 100644 Content.Shared/Power/Components/ChargerComponent.cs create mode 100644 Content.Shared/Power/EntitySystems/SharedBatterySystem.cs create mode 100644 Content.Shared/Power/EntitySystems/SharedChargerSystem.cs rename {Content.Server => Content.Shared}/SurveillanceCamera/Components/SurveillanceCameraComponent.cs (72%) diff --git a/Content.Client/Emp/EmpSystem.cs b/Content.Client/Emp/EmpSystem.cs index 5ed1022750..b32e370f1e 100644 --- a/Content.Client/Emp/EmpSystem.cs +++ b/Content.Client/Emp/EmpSystem.cs @@ -7,6 +7,18 @@ public sealed class EmpSystem : SharedEmpSystem { [Dependency] private readonly IRobustRandom _random = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + } + + private void OnStartup(Entity ent, ref ComponentStartup args) + { + // EmpPulseEvent.Affected will spawn the first visual effect directly when the emp is used + ent.Comp.TargetTime = Timing.CurTime + _random.NextFloat(0.8f, 1.2f) * ent.Comp.EffectCooldown; + } public override void Update(float frameTime) { base.Update(frameTime); @@ -16,7 +28,7 @@ public sealed class EmpSystem : SharedEmpSystem { if (Timing.CurTime > comp.TargetTime) { - comp.TargetTime = Timing.CurTime + _random.NextFloat(0.8f, 1.2f) * TimeSpan.FromSeconds(comp.EffectCooldown); + comp.TargetTime = Timing.CurTime + _random.NextFloat(0.8f, 1.2f) * comp.EffectCooldown; Spawn(EmpDisabledEffectPrototype, transform.Coordinates); } } diff --git a/Content.Client/Power/EntitySystems/BatterySystem.cs b/Content.Client/Power/EntitySystems/BatterySystem.cs new file mode 100644 index 0000000000..1d0b48003b --- /dev/null +++ b/Content.Client/Power/EntitySystems/BatterySystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Power.EntitySystems; + +namespace Content.Client.Power.EntitySystems; + +public sealed class BatterySystem : SharedBatterySystem; diff --git a/Content.Client/Power/EntitySystems/ChargerSystem.cs b/Content.Client/Power/EntitySystems/ChargerSystem.cs new file mode 100644 index 0000000000..efadde30e0 --- /dev/null +++ b/Content.Client/Power/EntitySystems/ChargerSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Power.EntitySystems; + +namespace Content.Client.Power.EntitySystems; + +public sealed class ChargerSystem : SharedChargerSystem; diff --git a/Content.Client/SurveillanceCamera/SurveillanceCameraSystem.cs b/Content.Client/SurveillanceCamera/SurveillanceCameraSystem.cs new file mode 100644 index 0000000000..5dbb9f8442 --- /dev/null +++ b/Content.Client/SurveillanceCamera/SurveillanceCameraSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.SurveillanceCamera; + +namespace Content.Client.SurveillanceCamera; + +public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem; diff --git a/Content.Client/VendingMachines/VendingMachineSystem.cs b/Content.Client/VendingMachines/VendingMachineSystem.cs index 7491912855..f116dd5107 100644 --- a/Content.Client/VendingMachines/VendingMachineSystem.cs +++ b/Content.Client/VendingMachines/VendingMachineSystem.cs @@ -33,6 +33,7 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem component.EjectEnd = state.EjectEnd; component.DenyEnd = state.DenyEnd; component.DispenseOnHitEnd = state.DispenseOnHitEnd; + component.Broken = state.Broken; // If all we did was update amounts then we can leave BUI buttons in place. var fullUiUpdate = !component.Inventory.Keys.SequenceEqual(state.Inventory.Keys) || diff --git a/Content.IntegrationTests/Tests/Power/PowerTest.cs b/Content.IntegrationTests/Tests/Power/PowerTest.cs index a448427d05..ab8a421c03 100644 --- a/Content.IntegrationTests/Tests/Power/PowerTest.cs +++ b/Content.IntegrationTests/Tests/Power/PowerTest.cs @@ -1,12 +1,11 @@ #nullable enable -using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; -using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Power.Nodes; using Content.Shared.Coordinates; using Content.Shared.NodeContainer; +using Content.Shared.Power.Components; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; diff --git a/Content.IntegrationTests/Tests/Power/StationPowerTests.cs b/Content.IntegrationTests/Tests/Power/StationPowerTests.cs index c9f9498750..3a0af28e48 100644 --- a/Content.IntegrationTests/Tests/Power/StationPowerTests.cs +++ b/Content.IntegrationTests/Tests/Power/StationPowerTests.cs @@ -5,6 +5,7 @@ using Content.Server.Maps; using Content.Server.Power.Components; using Content.Server.Power.NodeGroups; using Content.Server.Power.Pow3r; +using Content.Shared.Power.Components; using Content.Shared.NodeContainer; using Robust.Shared.EntitySerialization; diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index 44795d1fb2..176d4fd04c 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -24,6 +24,7 @@ using Content.Shared.Doors.Components; using Content.Shared.Hands.Components; using Content.Shared.Inventory; using Content.Shared.PDA; +using Content.Shared.Power.Components; using Content.Shared.Stacks; using Content.Shared.Station.Components; using Content.Shared.Verbs; diff --git a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs index f734d3eb3e..645b47d180 100644 --- a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs +++ b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs @@ -1,14 +1,10 @@ -using System.Linq; -using Content.Server.Emp; using Content.Shared.Clothing.Components; using Content.Shared.Clothing.EntitySystems; using Content.Shared.Emp; using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement.Components; -using Content.Shared.Inventory; using Content.Shared.Prototypes; using Robust.Shared.Prototypes; -using Robust.Shared.Random; namespace Content.Server.Clothing.Systems; @@ -16,15 +12,12 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IdentitySystem _identity = default!; - [Dependency] private readonly IRobustRandom _random = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnSelected); - - SubscribeLocalEvent(OnEmpPulse); } private void OnMapInit(EntityUid uid, ChameleonClothingComponent component, MapInitEvent args) @@ -37,21 +30,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem SetSelectedPrototype(uid, args.SelectedId, component: component); } - private void OnEmpPulse(EntityUid uid, ChameleonClothingComponent component, ref EmpPulseEvent args) - { - if (!component.AffectedByEmp) - return; - - if (component.EmpContinuous) - component.NextEmpChange = _timing.CurTime + TimeSpan.FromSeconds(1f / component.EmpChangeIntensity); - - var pick = GetRandomValidPrototype(component.Slot, component.RequireTag); - SetSelectedPrototype(uid, pick, component: component); - - args.Affected = true; - args.Disabled = true; - } - private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null) { if (!Resolve(uid, ref component)) @@ -64,7 +42,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem /// /// Change chameleon items name, description and sprite to mimic other entity prototype. /// - public void SetSelectedPrototype(EntityUid uid, string? protoId, bool forceUpdate = false, + public override void SetSelectedPrototype(EntityUid uid, string? protoId, bool forceUpdate = false, ChameleonClothingComponent? component = null) { if (!Resolve(uid, ref component, false)) @@ -88,14 +66,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem Dirty(uid, component); } - /// - /// Get a random prototype for a given slot. - /// - public string GetRandomValidPrototype(SlotFlags slot, string? tag = null) - { - return _random.Pick(GetValidTargets(slot, tag).ToList()); - } - public override void Update(float frameTime) { base.Update(frameTime); @@ -106,7 +76,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem if (!chameleon.EmpContinuous) continue; - if (_timing.CurTime < chameleon.NextEmpChange) + if (Timing.CurTime < chameleon.NextEmpChange) continue; // randomly pick cloth element from available and apply it diff --git a/Content.Server/Construction/Completions/BuildMech.cs b/Content.Server/Construction/Completions/BuildMech.cs index e11c79d851..c0b5921db9 100644 --- a/Content.Server/Construction/Completions/BuildMech.cs +++ b/Content.Server/Construction/Completions/BuildMech.cs @@ -1,7 +1,7 @@ using Content.Server.Mech.Systems; -using Content.Server.Power.Components; using Content.Shared.Construction; using Content.Shared.Mech.Components; +using Content.Shared.Power.Components; using JetBrains.Annotations; using Robust.Server.Containers; using Robust.Shared.Containers; diff --git a/Content.Server/Emp/EmpSystem.cs b/Content.Server/Emp/EmpSystem.cs index 67f9cabd42..38ab09e1cf 100644 --- a/Content.Server/Emp/EmpSystem.cs +++ b/Content.Server/Emp/EmpSystem.cs @@ -2,16 +2,11 @@ using Content.Server.Power.EntitySystems; using Content.Server.Radio; using Content.Server.SurveillanceCamera; using Content.Shared.Emp; -using Robust.Shared.Map; namespace Content.Server.Emp; public sealed class EmpSystem : SharedEmpSystem { - [Dependency] private readonly EntityLookupSystem _lookup = default!; - - public const string EmpPulseEffectPrototype = "EffectEmpPulse"; - public override void Initialize() { base.Initialize(); @@ -22,84 +17,6 @@ public sealed class EmpSystem : SharedEmpSystem SubscribeLocalEvent(OnCameraSetActive); } - public override void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration) - { - foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range)) - { - TryEmpEffects(uid, energyConsumption, duration); - } - Spawn(EmpPulseEffectPrototype, coordinates); - } - - /// - /// Triggers an EMP pulse at the given location, by first raising an , then a raising on all entities in range. - /// - /// The location to trigger the EMP pulse at. - /// The range of the EMP pulse. - /// The amount of energy consumed by the EMP pulse. - /// The duration of the EMP effects. - public void EmpPulse(EntityCoordinates coordinates, float range, float energyConsumption, float duration) - { - foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range)) - { - TryEmpEffects(uid, energyConsumption, duration); - } - Spawn(EmpPulseEffectPrototype, coordinates); - } - - /// - /// Attempts to apply the effects of an EMP pulse onto an entity by first raising an , followed by raising a on it. - /// - /// The entity to apply the EMP effects on. - /// The amount of energy consumed by the EMP. - /// The duration of the EMP effects. - public void TryEmpEffects(EntityUid uid, float energyConsumption, float duration) - { - var attemptEv = new EmpAttemptEvent(); - RaiseLocalEvent(uid, attemptEv); - if (attemptEv.Cancelled) - return; - - DoEmpEffects(uid, energyConsumption, duration); - } - - /// - /// Applies the effects of an EMP pulse onto an entity by raising a on it. - /// - /// The entity to apply the EMP effects on. - /// The amount of energy consumed by the EMP. - /// The duration of the EMP effects. - public void DoEmpEffects(EntityUid uid, float energyConsumption, float duration) - { - var ev = new EmpPulseEvent(energyConsumption, false, false, TimeSpan.FromSeconds(duration)); - RaiseLocalEvent(uid, ref ev); - - if (ev.Affected) - Spawn(EmpDisabledEffectPrototype, Transform(uid).Coordinates); - - if (!ev.Disabled) - return; - - var disabled = EnsureComp(uid); - disabled.DisabledUntil = Timing.CurTime + TimeSpan.FromSeconds(duration); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp)) - { - if (comp.DisabledUntil < Timing.CurTime) - { - RemComp(uid); - var ev = new EmpDisabledRemoved(); - RaiseLocalEvent(uid, ref ev); - } - } - } - private void OnRadioSendAttempt(EntityUid uid, EmpDisabledComponent component, ref RadioSendAttemptEvent args) { args.Cancelled = true; @@ -120,14 +37,3 @@ public sealed class EmpSystem : SharedEmpSystem args.Cancelled = true; } } - -/// -/// Raised on an entity before . Cancel this to prevent the emp event being raised. -/// -public sealed partial class EmpAttemptEvent : CancellableEntityEventArgs; - -[ByRefEvent] -public record struct EmpPulseEvent(float EnergyConsumption, bool Affected, bool Disabled, TimeSpan Duration); - -[ByRefEvent] -public record struct EmpDisabledRemoved(); diff --git a/Content.Server/Holosign/HolosignSystem.cs b/Content.Server/Holosign/HolosignSystem.cs index 58ed77ecf8..beb5e909c0 100644 --- a/Content.Server/Holosign/HolosignSystem.cs +++ b/Content.Server/Holosign/HolosignSystem.cs @@ -1,8 +1,8 @@ using Content.Shared.Examine; using Content.Shared.Coordinates.Helpers; -using Content.Server.Power.Components; using Content.Server.PowerCell; using Content.Shared.Interaction; +using Content.Shared.Power.Components; using Content.Shared.Storage; namespace Content.Server.Holosign; @@ -12,7 +12,6 @@ public sealed class HolosignSystem : EntitySystem [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - public override void Initialize() { base.Initialize(); diff --git a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs index 1b7b614665..0aea245c79 100644 --- a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs +++ b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Examine; using Content.Shared.Light; using Content.Shared.Light.Components; using Content.Shared.Power; +using Content.Shared.Power.Components; using Content.Shared.Station.Components; using Robust.Server.GameObjects; using Color = Robust.Shared.Maths.Color; diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index 948c44cd75..d1c231e490 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -1,4 +1,3 @@ -using Content.Server.Emp; using Content.Server.Ghost; using Content.Shared.Light.Components; using Content.Shared.Light.EntitySystems; @@ -16,8 +15,6 @@ public sealed class PoweredLightSystem : SharedPoweredLightSystem SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnGhostBoo); - - SubscribeLocalEvent(OnEmpPulse); } private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args) @@ -55,10 +52,4 @@ public sealed class PoweredLightSystem : SharedPoweredLightSystem // need this to update visualizers UpdateLight(uid, light); } - - private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args) - { - if (TryDestroyBulb(uid, component)) - args.Affected = true; - } } diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 7b0c2f6472..917f4f5035 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -2,7 +2,6 @@ using System.Linq; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Systems; using Content.Server.Mech.Components; -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.ActionBlocker; using Content.Shared.Damage; @@ -14,6 +13,7 @@ using Content.Shared.Mech.Components; using Content.Shared.Mech.EntitySystems; using Content.Shared.Movement.Events; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.Tools; using Content.Shared.Tools.Components; using Content.Shared.Tools.Systems; diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index 7af093b178..72c3f5ecd9 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -1,8 +1,6 @@ using Content.Server.DeviceNetwork.Systems; -using Content.Server.Emp; using Content.Server.Medical.CrewMonitoring; using Content.Shared.DeviceNetwork.Components; -using Content.Shared.Medical.SuitSensor; using Content.Shared.Medical.SuitSensors; using Robust.Shared.Timing; @@ -14,14 +12,6 @@ public sealed class SuitSensorSystem : SharedSuitSensorSystem [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; [Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnEmpPulse); - SubscribeLocalEvent(OnEmpFinished); - } - public override void Update(float frameTime) { base.Update(frameTime); @@ -70,22 +60,4 @@ public sealed class SuitSensorSystem : SharedSuitSensorSystem _deviceNetworkSystem.QueuePacket(uid, sensor.ConnectedServer, payload, device: device); } } - - private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) - { - args.Affected = true; - args.Disabled = true; - - ent.Comp.PreviousMode = ent.Comp.Mode; - SetSensor(ent.AsNullable(), SuitSensorMode.SensorOff, null); - - ent.Comp.PreviousControlsLocked = ent.Comp.ControlsLocked; - ent.Comp.ControlsLocked = true; - } - - private void OnEmpFinished(Entity ent, ref EmpDisabledRemoved args) - { - SetSensor(ent.AsNullable(), ent.Comp.PreviousMode, null); - ent.Comp.ControlsLocked = ent.Comp.PreviousControlsLocked; - } } diff --git a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs index 71e38ed3f6..e563386608 100644 --- a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs +++ b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs @@ -6,7 +6,7 @@ using Content.Shared.Interaction; using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Systems; using Content.Shared.Popups; -using Robust.Shared.Audio; +using Content.Shared.Power.Components; using Robust.Shared.Audio.Systems; namespace Content.Server.Ninja.Systems; diff --git a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs index 62d3d0e3ca..399d94e8f7 100644 --- a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs +++ b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs @@ -1,11 +1,11 @@ -using Content.Server.Emp; using Content.Server.Ninja.Events; using Content.Server.Power.Components; using Content.Server.PowerCell; +using Content.Shared.Emp; using Content.Shared.Hands.EntitySystems; using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Systems; -using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.PowerCell.Components; using Robust.Shared.Containers; @@ -16,7 +16,7 @@ namespace Content.Server.Ninja.Systems; /// public sealed class NinjaSuitSystem : SharedNinjaSuitSystem { - [Dependency] private readonly EmpSystem _emp = default!; + [Dependency] private readonly SharedEmpSystem _emp = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SpaceNinjaSystem _ninja = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; @@ -30,7 +30,6 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem base.Initialize(); SubscribeLocalEvent(OnSuitInsertAttempt); - SubscribeLocalEvent(OnEmpAttempt); SubscribeLocalEvent(OnRecallKatana); SubscribeLocalEvent(OnEmp); } @@ -44,7 +43,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem // raise event to let ninja components get starting battery _ninja.GetNinjaBattery(user.Owner, out var uid, out var _); - if (uid is not {} battery_uid) + if (uid is not { } battery_uid) return; var ev = new NinjaBatteryChangedEvent(battery_uid, ent.Owner); @@ -96,17 +95,10 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem // if a cell is able to automatically recharge, boost the score drastically depending on the recharge rate, // this is to ensure a ninja can still upgrade to a micro reactor cell even if they already have a medium or high. if (TryComp(uid, out var selfcomp) && selfcomp.AutoRecharge) - return battcomp.MaxCharge + (selfcomp.AutoRechargeRate*AutoRechargeValue); + return battcomp.MaxCharge + selfcomp.AutoRechargeRate * AutoRechargeValue; return battcomp.MaxCharge; } - private void OnEmpAttempt(EntityUid uid, NinjaSuitComponent comp, EmpAttemptEvent args) - { - // ninja suit (battery) is immune to emp - // powercell relays the event to suit - args.Cancel(); - } - protected override void UserUnequippedSuit(Entity ent, Entity user) { base.UserUnequippedSuit(ent, user); @@ -144,6 +136,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem Popup.PopupEntity(Loc.GetString(message), user, user); } + // TODO: Move this to shared when power cells are predicted. private void OnEmp(Entity ent, ref NinjaEmpEvent args) { var (uid, comp) = ent; @@ -159,7 +152,6 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem if (CheckDisabled(ent, user)) return; - var coords = _transform.GetMapCoordinates(user); - _emp.EmpPulse(coords, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration); + _emp.EmpPulse(Transform(user).Coordinates, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration, user); } } diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs index 1ece045774..ff88926723 100644 --- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs +++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs @@ -1,26 +1,20 @@ using Content.Server.Communications; -using Content.Server.Chat.Managers; using Content.Server.CriminalRecords.Systems; -using Content.Server.GameTicking.Rules.Components; using Content.Server.Objectives.Components; using Content.Server.Objectives.Systems; -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.PowerCell; using Content.Server.Research.Systems; -using Content.Server.Roles; using Content.Shared.Alert; using Content.Shared.Doors.Components; using Content.Shared.IdentityManagement; using Content.Shared.Mind; using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Systems; +using Content.Shared.Power.Components; using Content.Shared.Popups; using Content.Shared.Rounding; -using Robust.Shared.Audio; -using Robust.Shared.Player; using System.Diagnostics.CodeAnalysis; -using Robust.Shared.Audio.Systems; namespace Content.Server.Ninja.Systems; diff --git a/Content.Server/Power/Components/BatteryChargerComponent.cs b/Content.Server/Power/Components/BatteryChargerComponent.cs index 99284e44ac..7f3dd39524 100644 --- a/Content.Server/Power/Components/BatteryChargerComponent.cs +++ b/Content.Server/Power/Components/BatteryChargerComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Power.NodeGroups; +using Content.Shared.Power.Components; namespace Content.Server.Power.Components { diff --git a/Content.Server/Power/Components/BatteryComponent.cs b/Content.Server/Power/Components/BatteryComponent.cs deleted file mode 100644 index 96571bcca3..0000000000 --- a/Content.Server/Power/Components/BatteryComponent.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Content.Server.Power.EntitySystems; -using Content.Shared.Guidebook; - -namespace Content.Server.Power.Components -{ - /// - /// Battery node on the pow3r network. Needs other components to connect to actual networks. - /// - [RegisterComponent] - [Virtual] - [Access(typeof(BatterySystem))] - public partial class BatteryComponent : Component - { - public string SolutionName = "battery"; - - /// - /// Maximum charge of the battery in joules (ie. watt seconds) - /// - [DataField] - [GuidebookData] - public float MaxCharge; - - /// - /// Current charge of the battery in joules (ie. watt seconds) - /// - [DataField("startingCharge")] - public float CurrentCharge; - - /// - /// The price per one joule. Default is 1 credit for 10kJ. - /// - [DataField] - public float PricePerJoule = 0.0001f; - } - - /// - /// Raised when a battery's charge or capacity changes (capacity affects relative charge percentage). - /// - [ByRefEvent] - public readonly record struct ChargeChangedEvent(float Charge, float MaxCharge); - - /// - /// Raised when it is necessary to get information about battery charges. - /// - [ByRefEvent] - public sealed class GetChargeEvent : EntityEventArgs - { - public float CurrentCharge; - public float MaxCharge; - } - - /// - /// Raised when it is necessary to change the current battery charge to a some value. - /// - [ByRefEvent] - public sealed class ChangeChargeEvent : EntityEventArgs - { - public float OriginalValue; - public float ResidualValue; - - public ChangeChargeEvent(float value) - { - OriginalValue = value; - ResidualValue = value; - } - } -} diff --git a/Content.Server/Power/Components/ChargerComponent.cs b/Content.Server/Power/Components/ChargerComponent.cs deleted file mode 100644 index 4a3c83ae03..0000000000 --- a/Content.Server/Power/Components/ChargerComponent.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Content.Shared.Power; -using Content.Shared.Whitelist; - -namespace Content.Server.Power.Components -{ - [RegisterComponent] - public sealed partial class ChargerComponent : Component - { - [ViewVariables] - public CellChargerStatus Status; - - /// - /// The charge rate of the charger, in watts - /// - [DataField("chargeRate")] - public float ChargeRate = 20.0f; - - /// - /// The container ID that is holds the entities being charged. - /// - [DataField("slotId", required: true)] - public string SlotId = string.Empty; - - /// - /// A whitelist for what entities can be charged by this Charger. - /// - [DataField("whitelist")] - public EntityWhitelist? Whitelist; - - /// - /// Indicates whether the charger is portable and thus subject to EMP effects - /// and bypasses checks for transform, anchored, and ApcPowerReceiverComponent. - /// - [DataField] - public bool Portable = false; - } -} diff --git a/Content.Server/Power/EntitySystems/ApcSystem.cs b/Content.Server/Power/EntitySystems/ApcSystem.cs index 29c1431179..ed7ec0e225 100644 --- a/Content.Server/Power/EntitySystems/ApcSystem.cs +++ b/Content.Server/Power/EntitySystems/ApcSystem.cs @@ -1,11 +1,12 @@ -using Content.Server.Emp; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.Pow3r; using Content.Shared.Access.Systems; using Content.Shared.APC; using Content.Shared.Emag.Systems; +using Content.Shared.Emp; using Content.Shared.Popups; +using Content.Shared.Power; using Content.Shared.Rounding; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -203,6 +204,9 @@ public sealed class ApcSystem : EntitySystem return ApcExternalPowerState.Good; } + // TODO: This subscription should be in shared. + // But I am not moving ApcComponent to shared, this PR already got soaped enough and that component uses several layers of OOP. + // At least the EMP visuals won't mispredict, since all APCs also have the BatteryComponent, which also has a EMP effect and is in shared. private void OnEmpPulse(EntityUid uid, ApcComponent component, ref EmpPulseEvent args) { if (component.MainBreakerEnabled) diff --git a/Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs b/Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs index 33e3f8ff2c..83ff28646d 100644 --- a/Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs +++ b/Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Power.Components; using Content.Shared.Database; using Content.Shared.Power; +using Content.Shared.Power.Components; using Robust.Server.GameObjects; namespace Content.Server.Power.EntitySystems; diff --git a/Content.Server/Power/EntitySystems/BatterySystem.cs b/Content.Server/Power/EntitySystems/BatterySystem.cs index f4e48f4a3d..28b14f6925 100644 --- a/Content.Server/Power/EntitySystems/BatterySystem.cs +++ b/Content.Server/Power/EntitySystems/BatterySystem.cs @@ -1,7 +1,9 @@ -using Content.Server.Emp; using Content.Server.Power.Components; using Content.Shared.Cargo; using Content.Shared.Examine; +using Content.Shared.Power; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; using Content.Shared.Rejuvenate; using JetBrains.Annotations; using Robust.Shared.Utility; @@ -10,7 +12,7 @@ using Robust.Shared.Timing; namespace Content.Server.Power.EntitySystems { [UsedImplicitly] - public sealed class BatterySystem : EntitySystem + public sealed class BatterySystem : SharedBatterySystem { [Dependency] private readonly IGameTiming _timing = default!; @@ -22,7 +24,6 @@ namespace Content.Server.Power.EntitySystems SubscribeLocalEvent(OnNetBatteryRejuvenate); SubscribeLocalEvent(OnBatteryRejuvenate); SubscribeLocalEvent(CalculateBatteryPrice); - SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnChangeCharge); SubscribeLocalEvent(OnGetCharge); @@ -50,7 +51,7 @@ namespace Content.Server.Power.EntitySystems if (effectiveMax == 0) effectiveMax = 1; var chargeFraction = batteryComponent.CurrentCharge / effectiveMax; - var chargePercentRounded = (int) (chargeFraction * 100); + var chargePercentRounded = (int)(chargeFraction * 100); args.PushMarkup( Loc.GetString( "examinable-battery-component-examine-detail", @@ -108,15 +109,6 @@ namespace Content.Server.Power.EntitySystems { args.Price += component.CurrentCharge * component.PricePerJoule; } - - private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseEvent args) - { - args.Affected = true; - UseCharge(uid, args.EnergyConsumption, component); - // Apply a cooldown to the entity's self recharge if needed to avoid it immediately self recharging after an EMP. - TrySetChargeCooldown(uid); - } - private void OnChangeCharge(Entity entity, ref ChangeChargeEvent args) { if (args.ResidualValue == 0) @@ -131,7 +123,7 @@ namespace Content.Server.Power.EntitySystems args.MaxCharge += entity.Comp.MaxCharge; } - public float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null) + public override float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null) { if (value <= 0 || !Resolve(uid, ref battery) || battery.CurrentCharge == 0) return 0; @@ -139,7 +131,7 @@ namespace Content.Server.Power.EntitySystems return ChangeCharge(uid, -value, battery); } - public void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null) + public override void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null) { if (!Resolve(uid, ref battery)) return; @@ -174,7 +166,7 @@ namespace Content.Server.Power.EntitySystems /// /// Changes the current battery charge by some value /// - public float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null) + public override float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null) { if (!Resolve(uid, ref battery)) return 0; @@ -190,10 +182,7 @@ namespace Content.Server.Power.EntitySystems return delta; } - /// - /// Checks if the entity has a self recharge and puts it on cooldown if applicable. - /// - public void TrySetChargeCooldown(EntityUid uid, float value = -1) + public override void TrySetChargeCooldown(EntityUid uid, float value = -1) { if (!TryComp(uid, out var batteryself)) return; @@ -228,7 +217,7 @@ namespace Content.Server.Power.EntitySystems /// /// If sufficient charge is available on the battery, use it. Otherwise, don't. /// - public bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null) + public override bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null) { if (!Resolve(uid, ref battery, false) || value > battery.CurrentCharge) return false; diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs index c128c846fb..e8dc9e9962 100644 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -1,8 +1,9 @@ using Content.Server.Power.Components; -using Content.Server.Emp; -using Content.Server.PowerCell; using Content.Shared.Examine; +using Content.Server.PowerCell; using Content.Shared.Power; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; using Content.Shared.PowerCell.Components; using Content.Shared.Emp; using JetBrains.Annotations; @@ -15,7 +16,7 @@ using Content.Shared.Whitelist; namespace Content.Server.Power.EntitySystems; [UsedImplicitly] -internal sealed class ChargerSystem : EntitySystem +public sealed class ChargerSystem : SharedChargerSystem { [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; @@ -25,6 +26,8 @@ internal sealed class ChargerSystem : EntitySystem public override void Initialize() { + base.Initialize(); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnInserted); @@ -32,8 +35,6 @@ internal sealed class ChargerSystem : EntitySystem SubscribeLocalEvent(OnInsertAttempt); SubscribeLocalEvent(OnEntityStorageInsertAttempt); SubscribeLocalEvent(OnChargerExamine); - - SubscribeLocalEvent(OnEmpPulse); } private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStartup args) @@ -46,7 +47,7 @@ internal sealed class ChargerSystem : EntitySystem using (args.PushGroup(nameof(ChargerComponent))) { // rate at which the charger charges - args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int) component.ChargeRate))); + args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int)component.ChargeRate))); // try to get contents of the charger if (!_container.TryGetContainer(uid, component.SlotId, out var container)) @@ -70,7 +71,7 @@ internal sealed class ChargerSystem : EntitySystem continue; var chargePercentage = (battery.CurrentCharge / battery.MaxCharge) * 100; - args.PushMarkup(Loc.GetString("charger-content", ("chargePercentage", (int) chargePercentage))); + args.PushMarkup(Loc.GetString("charger-content", ("chargePercentage", (int)chargePercentage))); } } } @@ -194,12 +195,6 @@ internal sealed class ChargerSystem : EntitySystem } } - private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args) - { - args.Affected = true; - args.Disabled = true; - } - private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component) { if (!component.Portable) diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs index be79d64fc9..25757360b3 100644 --- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs @@ -1,6 +1,4 @@ -using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; -using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; using Content.Server.Power.Nodes; using Content.Server.Power.NodeGroups; @@ -9,6 +7,7 @@ using Content.Shared.GameTicking.Components; using Content.Shared.Pinpointer; using Content.Shared.Station.Components; using Content.Shared.Power; +using Content.Shared.Power.Components; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Map.Components; diff --git a/Content.Server/Power/EntitySystems/RiggableSystem.cs b/Content.Server/Power/EntitySystems/RiggableSystem.cs index 26eaca80fe..0f8b32865b 100644 --- a/Content.Server/Power/EntitySystems/RiggableSystem.cs +++ b/Content.Server/Power/EntitySystems/RiggableSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Kitchen.Components; using Content.Server.Power.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Database; +using Content.Shared.Power.Components; using Content.Shared.Rejuvenate; namespace Content.Server.Power.EntitySystems; diff --git a/Content.Server/Power/SMES/SmesSystem.cs b/Content.Server/Power/SMES/SmesSystem.cs index ebd56ee3e1..15c40b3c92 100644 --- a/Content.Server/Power/SMES/SmesSystem.cs +++ b/Content.Server/Power/SMES/SmesSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Power; +using Content.Shared.Power.Components; using Content.Shared.Rounding; using Content.Shared.SMES; using JetBrains.Annotations; diff --git a/Content.Server/Power/SetBatteryPercentCommand.cs b/Content.Server/Power/SetBatteryPercentCommand.cs index 7ceee20172..03d6d30f8a 100644 --- a/Content.Server/Power/SetBatteryPercentCommand.cs +++ b/Content.Server/Power/SetBatteryPercentCommand.cs @@ -1,7 +1,7 @@ using Content.Server.Administration; -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Administration; +using Content.Shared.Power.Components; using Robust.Shared.Console; namespace Content.Server.Power diff --git a/Content.Server/PowerCell/PowerCellSystem.Draw.cs b/Content.Server/PowerCell/PowerCellSystem.Draw.cs index 9156d30b1f..40e54ba13a 100644 --- a/Content.Server/PowerCell/PowerCellSystem.Draw.cs +++ b/Content.Server/PowerCell/PowerCellSystem.Draw.cs @@ -1,4 +1,4 @@ -using Content.Server.Power.Components; +using Content.Shared.Power; using Content.Shared.PowerCell; using Content.Shared.PowerCell.Components; diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs index 01767d6c41..6c00cdd300 100644 --- a/Content.Server/PowerCell/PowerCellSystem.cs +++ b/Content.Server/PowerCell/PowerCellSystem.cs @@ -1,17 +1,17 @@ -using Content.Server.Emp; +using System.Diagnostics.CodeAnalysis; +using Content.Server.Kitchen.Components; using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared.Containers.ItemSlots; using Content.Shared.Examine; +using Content.Shared.Popups; +using Content.Shared.Power; +using Content.Shared.Power.Components; using Content.Shared.PowerCell; using Content.Shared.PowerCell.Components; using Content.Shared.Rounding; +using Content.Shared.UserInterface; using Robust.Shared.Containers; -using System.Diagnostics.CodeAnalysis; -using Content.Server.Kitchen.Components; -using Content.Server.Power.EntitySystems; -using Content.Server.UserInterface; -using Content.Shared.Containers.ItemSlots; -using Content.Shared.Popups; -using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; namespace Content.Server.PowerCell; @@ -34,7 +34,6 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem SubscribeLocalEvent(OnChargeChanged); SubscribeLocalEvent(OnCellExamined); - SubscribeLocalEvent(OnCellEmpAttempt); SubscribeLocalEvent(OnDrawChargeChanged); SubscribeLocalEvent(OnDrawCellChanged); @@ -221,14 +220,6 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem OnBatteryExamined(uid, battery, args); } - private void OnCellEmpAttempt(EntityUid uid, PowerCellComponent component, EmpAttemptEvent args) - { - var parent = Transform(uid).ParentUid; - // relay the attempt event to the slot so it can cancel it - if (HasComp(parent)) - RaiseLocalEvent(parent, args); - } - private void OnCellSlotExamined(EntityUid uid, PowerCellSlotComponent component, ExaminedEvent args) { TryGetBatteryFromSlot(uid, out var battery); diff --git a/Content.Server/PowerSink/PowerSinkSystem.cs b/Content.Server/PowerSink/PowerSinkSystem.cs index ef08240c5c..2bf9046cc3 100644 --- a/Content.Server/PowerSink/PowerSinkSystem.cs +++ b/Content.Server/PowerSink/PowerSinkSystem.cs @@ -1,13 +1,13 @@ -using Content.Server.Explosion.EntitySystems; +using Content.Server.Chat.Systems; +using Content.Server.Explosion.EntitySystems; using Content.Server.Power.Components; -using Content.Shared.Examine; -using Robust.Shared.Utility; -using Content.Server.Chat.Systems; +using Content.Server.Power.EntitySystems; using Content.Server.Station.Systems; -using Robust.Shared.Timing; -using Robust.Shared.Audio; +using Content.Shared.Examine; +using Content.Shared.Power.Components; using Robust.Shared.Audio.Systems; -using Content.Server.Power.EntitySystems; +using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.PowerSink { diff --git a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs index 91abc9efd5..4d29e2f413 100644 --- a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs +++ b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Chat.Systems; -using Content.Server.Emp; using Content.Shared.Inventory.Events; using Content.Shared.Radio; using Content.Shared.Radio.Components; @@ -21,8 +20,6 @@ public sealed class HeadsetSystem : SharedHeadsetSystem SubscribeLocalEvent(OnKeysChanged); SubscribeLocalEvent(OnSpeak); - - SubscribeLocalEvent(OnEmpPulse); } private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args) @@ -69,7 +66,6 @@ public sealed class HeadsetSystem : SharedHeadsetSystem protected override void OnGotUnequipped(EntityUid uid, HeadsetComponent component, GotUnequippedEvent args) { base.OnGotUnequipped(uid, component, args); - component.IsEquipped = false; RemComp(uid); RemComp(args.Equipee); } @@ -82,6 +78,9 @@ public sealed class HeadsetSystem : SharedHeadsetSystem if (component.Enabled == value) return; + component.Enabled = value; + Dirty(uid, component); + if (!value) { RemCompDeferred(uid); @@ -113,13 +112,4 @@ public sealed class HeadsetSystem : SharedHeadsetSystem if (TryComp(parent, out ActorComponent? actor)) _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel); } - - private void OnEmpPulse(EntityUid uid, HeadsetComponent component, ref EmpPulseEvent args) - { - if (component.Enabled) - { - args.Affected = true; - args.Disabled = true; - } - } } diff --git a/Content.Server/SensorMonitoring/BatterySensorComponent.cs b/Content.Server/SensorMonitoring/BatterySensorComponent.cs index 2f0c97e6ec..a5a48ddc91 100644 --- a/Content.Server/SensorMonitoring/BatterySensorComponent.cs +++ b/Content.Server/SensorMonitoring/BatterySensorComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Power.Components; +using Content.Shared.Power.Components; namespace Content.Server.SensorMonitoring; diff --git a/Content.Server/SensorMonitoring/BatterySensorSystem.cs b/Content.Server/SensorMonitoring/BatterySensorSystem.cs index 5047cd1d29..bd94868c5f 100644 --- a/Content.Server/SensorMonitoring/BatterySensorSystem.cs +++ b/Content.Server/SensorMonitoring/BatterySensorSystem.cs @@ -1,8 +1,8 @@ -using Content.Server.DeviceNetwork; -using Content.Server.DeviceNetwork.Systems; +using Content.Server.DeviceNetwork.Systems; using Content.Server.Power.Components; using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Events; +using Content.Shared.Power.Components; namespace Content.Server.SensorMonitoring; diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs index 73c5670c1e..4ee2a07d72 100644 --- a/Content.Server/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.DoAfter; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; +using Content.Shared.Power; using Content.Shared.Power.Components; using Content.Shared.Rejuvenate; using Content.Shared.Roles; diff --git a/Content.Server/Stunnable/Systems/StunbatonSystem.cs b/Content.Server/Stunnable/Systems/StunbatonSystem.cs index f043c452ca..7f8aa37036 100644 --- a/Content.Server/Stunnable/Systems/StunbatonSystem.cs +++ b/Content.Server/Stunnable/Systems/StunbatonSystem.cs @@ -7,6 +7,8 @@ using Content.Shared.Examine; using Content.Shared.Item.ItemToggle; using Content.Shared.Item.ItemToggle.Components; using Content.Shared.Popups; +using Content.Shared.Power; +using Content.Shared.Power.Components; using Content.Shared.Stunnable; namespace Content.Server.Stunnable.Systems diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs index 4029488159..1f298f32a4 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Chat.Systems; using Content.Shared.Speech; using Content.Shared.Speech.Components; +using Content.Shared.SurveillanceCamera.Components; using Content.Shared.Whitelist; using Robust.Shared.Player; using static Content.Server.Chat.Systems.ChatSystem; diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs index 7f7dbc6c97..7e275c78ce 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs @@ -1,13 +1,12 @@ using Content.Server.Administration.Logs; using Content.Server.DeviceNetwork.Systems; -using Content.Server.Emp; using Content.Shared.ActionBlocker; using Content.Shared.Database; using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Events; using Content.Shared.Power; using Content.Shared.SurveillanceCamera; -using Content.Shared.Verbs; +using Content.Shared.SurveillanceCamera.Components; using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -15,7 +14,7 @@ using Content.Shared.DeviceNetwork.Components; namespace Content.Server.SurveillanceCamera; -public sealed class SurveillanceCameraSystem : EntitySystem +public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; @@ -57,15 +56,13 @@ public sealed class SurveillanceCameraSystem : EntitySystem public override void Initialize() { + base.Initialize(); + SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnPacketReceived); SubscribeLocalEvent(OnSetName); SubscribeLocalEvent(OnSetNetwork); - SubscribeLocalEvent>(AddVerbs); - - SubscribeLocalEvent(OnEmpPulse); - SubscribeLocalEvent(OnEmpDisabledRemoved); } private void OnPacketReceived(EntityUid uid, SurveillanceCameraComponent component, DeviceNetworkPacketEvent args) @@ -131,26 +128,6 @@ public sealed class SurveillanceCameraSystem : EntitySystem } } - private void AddVerbs(EntityUid uid, SurveillanceCameraComponent component, GetVerbsEvent verbs) - { - if (!_actionBlocker.CanInteract(verbs.User, uid) || !_actionBlocker.CanComplexInteract(verbs.User)) - { - return; - } - - if (component.NameSet && component.NetworkSet) - { - return; - } - - AlternativeVerb verb = new(); - verb.Text = Loc.GetString("surveillance-camera-setup"); - verb.Act = () => OpenSetupInterface(uid, verbs.User, component); - verbs.Verbs.Add(verb); - } - - - private void OnPowerChanged(EntityUid camera, SurveillanceCameraComponent component, ref PowerChangedEvent args) { SetActive(camera, args.Powered, component); @@ -173,6 +150,7 @@ public sealed class SurveillanceCameraSystem : EntitySystem component.CameraId = args.Name; component.NameSet = true; + Dirty(uid, component); UpdateSetupInterface(uid, component); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor)} set the name of {ToPrettyString(uid)} to \"{args.Name}.\""); } @@ -198,10 +176,11 @@ public sealed class SurveillanceCameraSystem : EntitySystem _deviceNetworkSystem.SetReceiveFrequency(uid, frequency.Frequency); component.NetworkSet = true; + Dirty(uid, component); UpdateSetupInterface(uid, component); } - private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null) + protected override void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null) { if (!Resolve(uid, ref camera)) return; @@ -271,7 +250,7 @@ public sealed class SurveillanceCameraSystem : EntitySystem UpdateVisuals(camera, component); } - public void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null) + public override void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null) { if (!Resolve(camera, ref component)) { @@ -418,21 +397,6 @@ public sealed class SurveillanceCameraSystem : EntitySystem _appearance.SetData(uid, SurveillanceCameraVisualsKey.Key, key, appearance); } - - private void OnEmpPulse(EntityUid uid, SurveillanceCameraComponent component, ref EmpPulseEvent args) - { - if (component.Active) - { - args.Affected = true; - args.Disabled = true; - SetActive(uid, false); - } - } - - private void OnEmpDisabledRemoved(EntityUid uid, SurveillanceCameraComponent component, ref EmpDisabledRemoved args) - { - SetActive(uid, true); - } } public sealed class OnSurveillanceCameraViewerAddEvent : EntityEventArgs diff --git a/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs b/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs index 4fd2f9b6ed..f3cae90b40 100644 --- a/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs +++ b/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs @@ -1,7 +1,7 @@ -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Tesla.Components; using Content.Server.Lightning; +using Content.Shared.Power.Components; namespace Content.Server.Tesla.EntitySystems; diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index df204cf338..86a7b512b6 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -1,9 +1,7 @@ using System.Linq; using System.Numerics; using Content.Server.Cargo.Systems; -using Content.Server.Emp; using Content.Server.Power.Components; -using Content.Server.Power.EntitySystems; using Content.Server.Vocalization.Systems; using Content.Shared.Cargo; using Content.Shared.Damage; @@ -35,7 +33,6 @@ namespace Content.Server.VendingMachines SubscribeLocalEvent(OnBreak); SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnVendingPrice); - SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnTryVocalize); SubscribeLocalEvent(OnActivatableUIOpenAttempt); @@ -86,6 +83,7 @@ namespace Content.Server.VendingMachines private void OnBreak(EntityUid uid, VendingMachineComponent vendComponent, BreakageEventArgs eventArgs) { vendComponent.Broken = true; + Dirty(uid, vendComponent); TryUpdateVisualState((uid, vendComponent)); } @@ -94,6 +92,7 @@ namespace Content.Server.VendingMachines if (!args.DamageIncreased && component.Broken) { component.Broken = false; + Dirty(uid, component); TryUpdateVisualState((uid, component)); return; } @@ -257,16 +256,6 @@ namespace Content.Server.VendingMachines args.Price += priceSets.Max(); } - private void OnEmpPulse(EntityUid uid, VendingMachineComponent component, ref EmpPulseEvent args) - { - if (!component.Broken && this.IsPowered(uid, EntityManager)) - { - args.Affected = true; - args.Disabled = true; - component.NextEmpEject = Timing.CurTime; - } - } - private void OnTryVocalize(Entity ent, ref TryVocalizeEvent args) { args.Cancelled |= ent.Comp.Broken; diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs index 4865d08782..d697e2bef1 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs @@ -1,7 +1,6 @@ -using Content.Server.Power.Components; using Content.Shared.Damage; using Content.Shared.Damage.Events; -using Content.Shared.FixedPoint; +using Content.Shared.Power; using Content.Shared.PowerCell.Components; using Content.Shared.Projectiles; using Content.Shared.Weapons.Ranged; diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEEmpInAreaComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEEmpInAreaComponent.cs index 1e9489e9ec..88faf2fdcd 100644 --- a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEEmpInAreaComponent.cs +++ b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEEmpInAreaComponent.cs @@ -22,5 +22,5 @@ public sealed partial class XAEEmpInAreaComponent : Component /// Duration (in seconds) for which devices going to be disabled. /// [DataField] - public float DisableDuration = 60f; + public TimeSpan DisableDuration = TimeSpan.FromSeconds(60); } diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs index 5e9bf7352b..85ffc75627 100644 --- a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs +++ b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs @@ -1,6 +1,6 @@ -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Xenoarchaeology.Artifact.XAE.Components; +using Content.Shared.Power.Components; using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.XAE; diff --git a/Content.Shared/Anomaly/Effects/Components/ElectricityAnomalyComponent.cs b/Content.Shared/Anomaly/Effects/Components/ElectricityAnomalyComponent.cs index e48e0ae6a7..c05a3f6e41 100644 --- a/Content.Shared/Anomaly/Effects/Components/ElectricityAnomalyComponent.cs +++ b/Content.Shared/Anomaly/Effects/Components/ElectricityAnomalyComponent.cs @@ -61,5 +61,5 @@ public sealed partial class ElectricityAnomalyComponent : Component /// Duration of devices being disabled by the emp pulse upon going supercritical. /// [DataField, ViewVariables(VVAccess.ReadWrite)] - public float EmpDisabledDuration = 60f; + public TimeSpan EmpDisabledDuration = TimeSpan.FromSeconds(60); } diff --git a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs index 18b79bf52b..07aae61f83 100644 --- a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs @@ -2,13 +2,16 @@ using System.Linq; using Content.Shared.Access.Components; using Content.Shared.Clothing.Components; using Content.Shared.Contraband; +using Content.Shared.Emp; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Lock; using Content.Shared.Tag; using Content.Shared.Verbs; +using Robust.Shared.Network; using Robust.Shared.Prototypes; +using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -23,8 +26,11 @@ public abstract class SharedChameleonClothingSystem : EntitySystem [Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly TagSystem _tag = default!; - [Dependency] protected readonly IGameTiming _timing = default!; + [Dependency] protected readonly IGameTiming Timing = default!; [Dependency] private readonly LockSystem _lock = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] protected readonly SharedUserInterfaceSystem UI = default!; + [Dependency] private readonly INetManager _net = default!; private static readonly SlotFlags[] IgnoredSlots = { @@ -32,12 +38,12 @@ public abstract class SharedChameleonClothingSystem : EntitySystem SlotFlags.PREVENTEQUIP, SlotFlags.NONE }; + private static readonly SlotFlags[] Slots = Enum.GetValues().Except(IgnoredSlots).ToArray(); private readonly Dictionary> _data = new(); public readonly Dictionary> ValidVariants = new(); - [Dependency] protected readonly SharedUserInterfaceSystem UI = default!; private static readonly ProtoId WhitelistChameleonTag = "WhitelistChameleon"; @@ -47,6 +53,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); SubscribeLocalEvent>(OnVerb); + SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnPrototypeReload); PrepareAllVariants(); @@ -97,21 +104,21 @@ public abstract class SharedChameleonClothingSystem : EntitySystem // clothing sprite logic if (TryComp(uid, out ClothingComponent? clothing) && - proto.TryGetComponent("Clothing", out ClothingComponent? otherClothing)) + proto.TryGetComponent(out ClothingComponent? otherClothing, Factory)) { _clothingSystem.CopyVisuals(uid, otherClothing, clothing); } // appearance data logic if (TryComp(uid, out AppearanceComponent? appearance) && - proto.TryGetComponent("Appearance", out AppearanceComponent? appearanceOther)) + proto.TryGetComponent(out AppearanceComponent? appearanceOther, Factory)) { _appearance.AppendData(appearanceOther, uid); Dirty(uid, appearance); } // properly mark contraband - if (proto.TryGetComponent("Contraband", out ContrabandComponent? contra)) + if (proto.TryGetComponent(out ContrabandComponent? contra, Factory)) { EnsureComp(uid, out var current); _contraband.CopyDetails(uid, contra, current); @@ -138,6 +145,24 @@ public abstract class SharedChameleonClothingSystem : EntitySystem }); } + private void OnEmpPulse(EntityUid uid, ChameleonClothingComponent component, ref EmpPulseEvent args) + { + if (!component.AffectedByEmp) + return; + + if (component.EmpContinuous) + component.NextEmpChange = Timing.CurTime + TimeSpan.FromSeconds(1f / component.EmpChangeIntensity); + + if (_net.IsServer) // needs RandomPredicted + { + var pick = GetRandomValidPrototype(component.Slot, component.RequireTag); + SetSelectedPrototype(uid, pick, component: component); + } + + args.Affected = true; + args.Disabled = true; + } + protected virtual void UpdateSprite(EntityUid uid, EntityPrototype proto) { } /// @@ -157,7 +182,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem return false; // check if it's valid clothing - if (!proto.TryGetComponent("Clothing", out ClothingComponent? clothing)) + if (!proto.TryGetComponent(out ClothingComponent? clothing, Factory)) return false; if (!clothing.Slots.HasFlag(chameleonSlot)) return false; @@ -187,6 +212,14 @@ public abstract class SharedChameleonClothingSystem : EntitySystem return validTargets; } + /// + /// Get a random prototype for a given slot. + /// + public string GetRandomValidPrototype(SlotFlags slot, string? tag = null) + { + return _random.Pick(GetValidTargets(slot, tag).ToList()); + } + protected void PrepareAllVariants() { _data.Clear(); @@ -215,4 +248,9 @@ public abstract class SharedChameleonClothingSystem : EntitySystem } } } + + // TODO: Predict and use component states for the UI + public virtual void SetSelectedPrototype(EntityUid uid, string? protoId, bool forceUpdate = false, + ChameleonClothingComponent? component = null) + { } } diff --git a/Content.Shared/Emp/EmpDisabledComponent.cs b/Content.Shared/Emp/EmpDisabledComponent.cs index 9e5a56de83..71748c0be3 100644 --- a/Content.Shared/Emp/EmpDisabledComponent.cs +++ b/Content.Shared/Emp/EmpDisabledComponent.cs @@ -5,24 +5,30 @@ namespace Content.Shared.Emp; /// /// While entity has this component it is "disabled" by EMP. -/// Add desired behaviour in other systems +/// Add desired behaviour in other systems. /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause] [Access(typeof(SharedEmpSystem))] public sealed partial class EmpDisabledComponent : Component { /// - /// Moment of time when component is removed and entity stops being "disabled" + /// Moment of time when the component is removed and entity stops being "disabled". /// - [DataField("timeLeft", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - [AutoPausedField] - public TimeSpan DisabledUntil; + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField] + public TimeSpan DisabledUntil = TimeSpan.Zero; - [DataField("effectCoolDown"), ViewVariables(VVAccess.ReadWrite)] - public float EffectCooldown = 3f; + /// + /// Default time between visual effect spawns. + /// This gets a random multiplier. + /// + [DataField, AutoNetworkedField] + public TimeSpan EffectCooldown = TimeSpan.FromSeconds(3); /// - /// When next effect will be spawned + /// When next effect will be spawned. + /// TODO: Particle system. /// [AutoPausedField] public TimeSpan TargetTime = TimeSpan.Zero; diff --git a/Content.Shared/Emp/SharedEmpSystem.cs b/Content.Shared/Emp/SharedEmpSystem.cs index deb2afd709..7e6ea58dbc 100644 --- a/Content.Shared/Emp/SharedEmpSystem.cs +++ b/Content.Shared/Emp/SharedEmpSystem.cs @@ -1,21 +1,58 @@ using Content.Shared.Examine; +using Content.Shared.Rejuvenate; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Timing; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; namespace Content.Shared.Emp; public abstract class SharedEmpSystem : EntitySystem { [Dependency] protected readonly IGameTiming Timing = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private HashSet _entSet = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnRejuvenate); } - protected const string EmpDisabledEffectPrototype = "EffectEmpDisabled"; + public static readonly EntProtoId EmpPulseEffectPrototype = "EffectEmpPulse"; + public static readonly EntProtoId EmpDisabledEffectPrototype = "EffectEmpDisabled"; + public static readonly SoundSpecifier EmpSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg"); + + /// + /// Triggers an EMP pulse at the given location, by first raising an , then by raising on all entities in range. + /// + /// The location to trigger the EMP pulse at. + /// The range of the EMP pulse. + /// The amount of energy consumed by the EMP pulse. In Joule. + /// The duration of the EMP effects. + /// The player that caused the effect. Used for predicted audio. + public void EmpPulse(MapCoordinates mapCoordinates, float range, float energyConsumption, TimeSpan duration, EntityUid? user = null) + { + foreach (var uid in _lookup.GetEntitiesInRange(mapCoordinates, range)) + { + TryEmpEffects(uid, energyConsumption, duration, user); + } + // TODO: replace with PredictedSpawn once it works with animated sprites + if (_net.IsServer) + Spawn(EmpPulseEffectPrototype, mapCoordinates); + + var coordinates = _transform.ToCoordinates(mapCoordinates); + _audio.PlayPredicted(EmpSound, coordinates, user); + } /// /// Triggers an EMP pulse at the given location, by first raising an , then a raising on all entities in range. @@ -24,12 +61,119 @@ public abstract class SharedEmpSystem : EntitySystem /// The range of the EMP pulse. /// The amount of energy consumed by the EMP pulse. /// The duration of the EMP effects. - public virtual void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration) + /// The player that caused the effect. Used for predicted audio. + public void EmpPulse(EntityCoordinates coordinates, float range, float energyConsumption, TimeSpan duration, EntityUid? user = null) + { + _entSet.Clear(); + _lookup.GetEntitiesInRange(coordinates, range, _entSet); + foreach (var uid in _entSet) + { + TryEmpEffects(uid, energyConsumption, duration, user); + } + // TODO: replace with PredictedSpawn once it works with animated sprites + if (_net.IsServer) + Spawn(EmpPulseEffectPrototype, coordinates); + + _audio.PlayPredicted(EmpSound, coordinates, user); + } + + /// + /// Attempts to apply the effects of an EMP pulse onto an entity by first raising an , followed by raising a on it. + /// + /// The entity to apply the EMP effects on. + /// The amount of energy consumed by the EMP. + /// The duration of the EMP effects. + /// The player that caused the EMP. For prediction purposes. + /// If the entity was affected by the EMP. + public bool TryEmpEffects(EntityUid uid, float energyConsumption, TimeSpan duration, EntityUid? user = null) { + var attemptEv = new EmpAttemptEvent(); + RaiseLocalEvent(uid, ref attemptEv); + if (attemptEv.Cancelled) + return false; + + return DoEmpEffects(uid, energyConsumption, duration, user); + } + + /// + /// Applies the effects of an EMP pulse onto an entity by raising a on it. + /// + /// The entity to apply the EMP effects on. + /// The amount of energy consumed by the EMP. + /// The duration of the EMP effects. + /// The player that caused the EMP. For prediction purposes. + /// If the entity was affected by the EMP. + public bool DoEmpEffects(EntityUid uid, float energyConsumption, TimeSpan duration, EntityUid? user = null) + { + var ev = new EmpPulseEvent(energyConsumption, false, false, duration, user); + RaiseLocalEvent(uid, ref ev); + + // TODO: replace with PredictedSpawn once it works with animated sprites + if (ev.Affected && _net.IsServer) + Spawn(EmpDisabledEffectPrototype, Transform(uid).Coordinates); + + if (!ev.Disabled) + return ev.Affected; + + var disabled = EnsureComp(uid); + disabled.DisabledUntil = Timing.CurTime + duration; + Dirty(uid, disabled); + + return ev.Affected; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = Timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (curTime < comp.DisabledUntil) + continue; + + RemComp(uid); + } } private void OnExamine(Entity ent, ref ExaminedEvent args) { args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine")); } + + private void OnRemove(Entity ent, ref ComponentRemove args) + { + var ev = new EmpDisabledRemovedEvent(); + RaiseLocalEvent(ent, ref ev); + } + + private void OnRejuvenate(Entity ent, ref RejuvenateEvent args) + { + RemCompDeferred(ent); + } } + +/// +/// Raised on an entity before . Cancel this to prevent the emp event being raised. +/// +[ByRefEvent] +public record struct EmpAttemptEvent(bool Cancelled); + +/// +/// Raised on an entity when it gets hit by an EMP Pulse. +/// +/// The amount of energy to remove from batteries. In Joule. +/// Set this is true in the subscription to spawn a visual effect at the entity's location. +/// Set this to ture in the subscription to add to the entity. +/// The duration the entity will be disabled. +/// The player that caused the EMP. For prediction purposes. + +[ByRefEvent] +public record struct EmpPulseEvent(float EnergyConsumption, bool Affected, bool Disabled, TimeSpan Duration, EntityUid? User); + +/// +/// Raised on an entity after is removed. +/// +[ByRefEvent] +public record struct EmpDisabledRemovedEvent(); diff --git a/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs b/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs index eee0aeb51b..0f9eacc58d 100644 --- a/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs +++ b/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs @@ -27,7 +27,7 @@ public sealed partial class EmpReactionEffect : EventEntityEffect [DataField("duration")] - public float DisableDuration = 15; + public TimeSpan DisableDuration = TimeSpan.FromSeconds(15); protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-emp-reaction-effect", ("chance", Probability)); diff --git a/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs index 65097f0d06..9ca0a1821b 100644 --- a/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs +++ b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.DeviceLinking.Events; using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Events; using Content.Shared.DoAfter; +using Content.Shared.Emp; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Light.Components; @@ -52,6 +53,7 @@ public abstract class SharedPoweredLightSystem : EntitySystem SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(HandleLightDamaged); + SubscribeLocalEvent(OnEmpPulse); } private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) @@ -230,29 +232,21 @@ public abstract class SharedPoweredLightSystem : EntitySystem /// /// Try to break bulb inside light fixture /// - public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) + public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null, EntityUid? user = null) { if (!Resolve(uid, ref light, false)) return false; - // if we aren't mapinited, - // just null the spawned bulb - if (LifeStage(uid) < EntityLifeStage.MapInitialized) - { - light.HasLampOnSpawn = null; - return true; - } - // check bulb state var bulbUid = GetBulb(uid, light); - if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb)) + if (bulbUid == null || !TryComp(bulbUid.Value, out var lightBulb)) return false; if (lightBulb.State == LightBulbState.Broken) return false; // break it _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); - _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); + _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb, user); UpdateLight(uid, light); return true; } @@ -327,13 +321,18 @@ public abstract class SharedPoweredLightSystem : EntitySystem /// /// Destroy the light bulb if the light took any damage. /// + /// + /// TODO: This should be an IThresholdBehaviour once DestructibleSystem is predicted. + /// public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args) { + if (GameTiming.ApplyingState) // The destruction is already networked on its own. + return; + // Was it being repaired, or did it take damage? if (args.DamageIncreased) { - // Eventually, this logic should all be done by this (or some other) system, not a component. - TryDestroyBulb(uid, component); + TryDestroyBulb(uid, component, args.Origin); } } @@ -348,6 +347,12 @@ public abstract class SharedPoweredLightSystem : EntitySystem UpdateLight(uid, component); } + private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args) + { + if (TryDestroyBulb(uid, component)) + args.Affected = true; + } + public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking) { if (light.IsBlinking == isNowBlinking) diff --git a/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs index 2ed1089ba8..6930b029b7 100644 --- a/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs +++ b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Clothing; using Content.Shared.Damage; using Content.Shared.DeviceNetwork; using Content.Shared.DoAfter; +using Content.Shared.Emp; using Content.Shared.Examine; using Content.Shared.GameTicking; using Content.Shared.Interaction; @@ -49,6 +50,8 @@ public abstract class SharedSuitSensorSystem : EntitySystem SubscribeLocalEvent(OnPlayerSpawn); SubscribeLocalEvent(OnEquipped); SubscribeLocalEvent(OnUnequipped); + SubscribeLocalEvent(OnEmpPulse); + SubscribeLocalEvent(OnEmpFinished); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent>(OnVerb); SubscribeLocalEvent(OnInsert); @@ -133,6 +136,25 @@ public abstract class SharedSuitSensorSystem : EntitySystem Dirty(ent); } + private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) + { + args.Affected = true; + args.Disabled = true; + + ent.Comp.PreviousMode = ent.Comp.Mode; + SetSensor(ent.AsNullable(), SuitSensorMode.SensorOff, null); + + ent.Comp.PreviousControlsLocked = ent.Comp.ControlsLocked; + ent.Comp.ControlsLocked = true; + // SetSensor already calls Dirty + } + + private void OnEmpFinished(Entity ent, ref EmpDisabledRemovedEvent args) + { + SetSensor(ent.AsNullable(), ent.Comp.PreviousMode, null); + ent.Comp.ControlsLocked = ent.Comp.PreviousControlsLocked; + } + private void OnExamine(Entity ent, ref ExaminedEvent args) { if (!args.IsInDetailsRange) diff --git a/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs b/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs index b20b7af2c9..19bee1b017 100644 --- a/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs +++ b/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs @@ -85,13 +85,13 @@ public sealed partial class SuitSensorComponent : Component /// /// The previous mode of the suit. This is used to restore the state when an EMP effect ends. /// - [DataField, ViewVariables] + [DataField, AutoNetworkedField, ViewVariables] public SuitSensorMode PreviousMode = SuitSensorMode.SensorOff; /// /// The previous locked status of the controls. This is used to restore the state when an EMP effect ends. /// This keeps prisoner jumpsuits/internal implants from becoming unlocked after an EMP. /// - [DataField, ViewVariables] + [DataField, AutoNetworkedField, ViewVariables] public bool PreviousControlsLocked = false; } diff --git a/Content.Shared/Ninja/Components/NinjaSuitComponent.cs b/Content.Shared/Ninja/Components/NinjaSuitComponent.cs index 8b477b2aa5..b1467b4146 100644 --- a/Content.Shared/Ninja/Components/NinjaSuitComponent.cs +++ b/Content.Shared/Ninja/Components/NinjaSuitComponent.cs @@ -72,10 +72,10 @@ public sealed partial class NinjaSuitComponent : Component public float EmpConsumption = 100000f; /// - /// How long the EMP effects last for, in seconds + /// How long the EMP effects last for /// [DataField] - public float EmpDuration = 60f; + public TimeSpan EmpDuration = TimeSpan.FromSeconds(60); } public sealed partial class RecallKatanaEvent : InstantActionEvent; diff --git a/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs b/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs index 3800d15b26..f316b16f6b 100644 --- a/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs +++ b/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs @@ -1,7 +1,7 @@ using Content.Shared.Actions; using Content.Shared.Clothing; using Content.Shared.Clothing.Components; -using Content.Shared.Clothing.EntitySystems; +using Content.Shared.Emp; using Content.Shared.Inventory.Events; using Content.Shared.Item.ItemToggle; using Content.Shared.Item.ItemToggle.Components; @@ -18,8 +18,8 @@ namespace Content.Shared.Ninja.Systems; public abstract class SharedNinjaSuitSystem : EntitySystem { [Dependency] private readonly ActionContainerSystem _actionContainer = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly ItemToggleSystem _toggle = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!; @@ -36,6 +36,7 @@ public abstract class SharedNinjaSuitSystem : EntitySystem SubscribeLocalEvent(OnCreateStarAttempt); SubscribeLocalEvent(OnActivateAttempt); SubscribeLocalEvent(OnUnequipped); + SubscribeLocalEvent(OnEmpAttempt); } private void OnEquipped(Entity ent, ref ClothingGotEquippedEvent args) @@ -168,7 +169,14 @@ public abstract class SharedNinjaSuitSystem : EntitySystem // mark the user as not wearing a suit _ninja.AssignSuit(user, null); // disable glove abilities - if (user.Comp.Gloves is {} uid) + if (user.Comp.Gloves is { } uid) _toggle.TryDeactivate(uid, user: user); } + + private void OnEmpAttempt(Entity ent, ref EmpAttemptEvent args) + { + // ninja suit (battery) is immune to emp + // powercell relays the event to suit + args.Cancelled = true; + } } diff --git a/Content.Shared/Power/ChargeEvents.cs b/Content.Shared/Power/ChargeEvents.cs new file mode 100644 index 0000000000..db412e91d9 --- /dev/null +++ b/Content.Shared/Power/ChargeEvents.cs @@ -0,0 +1,33 @@ +namespace Content.Shared.Power; + +/// +/// Raised when a battery's charge or capacity changes (capacity affects relative charge percentage). +/// +[ByRefEvent] +public readonly record struct ChargeChangedEvent(float Charge, float MaxCharge); + +/// +/// Raised when it is necessary to get information about battery charges. +/// +[ByRefEvent] +public sealed class GetChargeEvent : EntityEventArgs +{ + public float CurrentCharge; + public float MaxCharge; +} + +/// +/// Raised when it is necessary to change the current battery charge to a some value. +/// +[ByRefEvent] +public sealed class ChangeChargeEvent : EntityEventArgs +{ + public float OriginalValue; + public float ResidualValue; + + public ChangeChargeEvent(float value) + { + OriginalValue = value; + ResidualValue = value; + } +} diff --git a/Content.Shared/Power/Components/ApcPowerReceiverBatteryComponent.cs b/Content.Shared/Power/Components/ApcPowerReceiverBatteryComponent.cs index 02c8328fa1..92efabd7ab 100644 --- a/Content.Shared/Power/Components/ApcPowerReceiverBatteryComponent.cs +++ b/Content.Shared/Power/Components/ApcPowerReceiverBatteryComponent.cs @@ -6,7 +6,7 @@ namespace Content.Shared.Power.Components; /// /// Attached to APC powered entities that possess a rechargeable internal battery. /// If external power is interrupted, the entity will draw power from this battery instead. -/// Requires and to function. +/// Requires and to function. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [Access(typeof(SharedPowerNetSystem), typeof(SharedPowerReceiverSystem))] diff --git a/Content.Shared/Power/Components/BatteryComponent.cs b/Content.Shared/Power/Components/BatteryComponent.cs new file mode 100644 index 0000000000..6a2d2a5051 --- /dev/null +++ b/Content.Shared/Power/Components/BatteryComponent.cs @@ -0,0 +1,34 @@ +using Content.Shared.Power.EntitySystems; +using Content.Shared.Guidebook; + +namespace Content.Shared.Power.Components; + +/// +/// Battery node on the pow3r network. Needs other components to connect to actual networks. +/// +[RegisterComponent] +[Virtual] +[Access(typeof(SharedBatterySystem))] +public partial class BatteryComponent : Component +{ + public string SolutionName = "battery"; + + /// + /// Maximum charge of the battery in joules (ie. watt seconds) + /// + [DataField] + [GuidebookData] + public float MaxCharge; + + /// + /// Current charge of the battery in joules (ie. watt seconds) + /// + [DataField("startingCharge")] + public float CurrentCharge; + + /// + /// The price per one joule. Default is 1 credit for 10kJ. + /// + [DataField] + public float PricePerJoule = 0.0001f; +} diff --git a/Content.Shared/Power/Components/ChargerComponent.cs b/Content.Shared/Power/Components/ChargerComponent.cs new file mode 100644 index 0000000000..a3f2f8f424 --- /dev/null +++ b/Content.Shared/Power/Components/ChargerComponent.cs @@ -0,0 +1,36 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Power.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ChargerComponent : Component +{ + [ViewVariables] + public CellChargerStatus Status; + + /// + /// The charge rate of the charger, in watts + /// + [DataField] + public float ChargeRate = 20.0f; + + /// + /// The container ID that is holds the entities being charged. + /// + [DataField(required: true)] + public string SlotId = string.Empty; + + /// + /// A whitelist for what entities can be charged by this Charger. + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// Indicates whether the charger is portable and thus subject to EMP effects + /// and bypasses checks for transform, anchored, and ApcPowerReceiverComponent. + /// + [DataField] + public bool Portable = false; +} diff --git a/Content.Shared/Power/EntitySystems/SharedBatterySystem.cs b/Content.Shared/Power/EntitySystems/SharedBatterySystem.cs new file mode 100644 index 0000000000..90931ef9f1 --- /dev/null +++ b/Content.Shared/Power/EntitySystems/SharedBatterySystem.cs @@ -0,0 +1,44 @@ +using Content.Shared.Emp; +using Content.Shared.Power.Components; + +namespace Content.Shared.Power.EntitySystems; + +public abstract class SharedBatterySystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEmpPulse); + } + + private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseEvent args) + { + args.Affected = true; + UseCharge(uid, args.EnergyConsumption, component); + // Apply a cooldown to the entity's self recharge if needed to avoid it immediately self recharging after an EMP. + TrySetChargeCooldown(uid); + } + + public virtual float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null) + { + return 0f; + } + + public virtual void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null) { } + + public virtual float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null) + { + return 0f; + } + + /// + /// Checks if the entity has a self recharge and puts it on cooldown if applicable. + /// + public virtual void TrySetChargeCooldown(EntityUid uid, float value = -1) { } + + public virtual bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null) + { + return false; + } +} diff --git a/Content.Shared/Power/EntitySystems/SharedChargerSystem.cs b/Content.Shared/Power/EntitySystems/SharedChargerSystem.cs new file mode 100644 index 0000000000..a150436bef --- /dev/null +++ b/Content.Shared/Power/EntitySystems/SharedChargerSystem.cs @@ -0,0 +1,20 @@ +using Content.Shared.Emp; +using Content.Shared.Power.Components; + +namespace Content.Shared.Power.EntitySystems; + +public abstract class SharedChargerSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEmpPulse); + } + + private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args) + { + args.Affected = true; + args.Disabled = true; + } +} diff --git a/Content.Shared/PowerCell/SharedPowerCellSystem.cs b/Content.Shared/PowerCell/SharedPowerCellSystem.cs index 3398563e55..0b71a3c24d 100644 --- a/Content.Shared/PowerCell/SharedPowerCellSystem.cs +++ b/Content.Shared/PowerCell/SharedPowerCellSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Containers.ItemSlots; +using Content.Shared.Emp; using Content.Shared.PowerCell.Components; using Content.Shared.Rejuvenate; using Robust.Shared.Containers; @@ -22,6 +23,8 @@ public abstract class SharedPowerCellSystem : EntitySystem SubscribeLocalEvent(OnCellInserted); SubscribeLocalEvent(OnCellRemoved); SubscribeLocalEvent(OnCellInsertAttempt); + + SubscribeLocalEvent(OnCellEmpAttempt); } private void OnMapInit(Entity ent, ref MapInitEvent args) @@ -71,6 +74,14 @@ public abstract class SharedPowerCellSystem : EntitySystem RaiseLocalEvent(uid, new PowerCellChangedEvent(true), false); } + private void OnCellEmpAttempt(EntityUid uid, PowerCellComponent component, EmpAttemptEvent args) + { + var parent = Transform(uid).ParentUid; + // relay the attempt event to the slot so it can cancel it + if (HasComp(parent)) + RaiseLocalEvent(parent, ref args); + } + public void SetDrawEnabled(Entity ent, bool enabled) { if (!Resolve(ent, ref ent.Comp, false) || ent.Comp.Enabled == enabled) diff --git a/Content.Shared/Radio/Components/HeadsetComponent.cs b/Content.Shared/Radio/Components/HeadsetComponent.cs index 66a0ae8027..28911fb674 100644 --- a/Content.Shared/Radio/Components/HeadsetComponent.cs +++ b/Content.Shared/Radio/Components/HeadsetComponent.cs @@ -1,18 +1,20 @@ using Content.Shared.Inventory; +using Robust.Shared.GameStates; namespace Content.Shared.Radio.Components; /// -/// This component relays radio messages to the parent entity's chat when equipped. +/// This component relays radio messages to the parent entity's chat when equipped. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class HeadsetComponent : Component { - [DataField("enabled")] + [DataField, AutoNetworkedField] public bool Enabled = true; + [DataField, AutoNetworkedField] public bool IsEquipped = false; - [DataField("requiredSlot")] + [DataField, AutoNetworkedField] public SlotFlags RequiredSlot = SlotFlags.EARS; } diff --git a/Content.Shared/Radio/EntitySystems/SharedHeadsetSystem.cs b/Content.Shared/Radio/EntitySystems/SharedHeadsetSystem.cs index 8083c8cd3a..5526e8f133 100644 --- a/Content.Shared/Radio/EntitySystems/SharedHeadsetSystem.cs +++ b/Content.Shared/Radio/EntitySystems/SharedHeadsetSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.Emp; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Radio.Components; @@ -9,9 +10,11 @@ public abstract class SharedHeadsetSystem : EntitySystem public override void Initialize() { base.Initialize(); + SubscribeLocalEvent>(OnGetDefault); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnEmpPulse); } private void OnGetDefault(EntityUid uid, HeadsetComponent component, InventoryRelayedEvent args) @@ -23,16 +26,27 @@ public abstract class SharedHeadsetSystem : EntitySystem } if (TryComp(uid, out EncryptionKeyHolderComponent? keyHolder)) - args.Args.Channel ??= keyHolder.DefaultChannel; + args.Args.Channel ??= keyHolder.DefaultChannel; } protected virtual void OnGotEquipped(EntityUid uid, HeadsetComponent component, GotEquippedEvent args) { component.IsEquipped = args.SlotFlags.HasFlag(component.RequiredSlot); + Dirty(uid, component); } protected virtual void OnGotUnequipped(EntityUid uid, HeadsetComponent component, GotUnequippedEvent args) { component.IsEquipped = false; + Dirty(uid, component); + } + + private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) + { + if (ent.Comp.Enabled) + { + args.Affected = true; + args.Disabled = true; + } } } diff --git a/Content.Server/SurveillanceCamera/Components/SurveillanceCameraComponent.cs b/Content.Shared/SurveillanceCamera/Components/SurveillanceCameraComponent.cs similarity index 72% rename from Content.Server/SurveillanceCamera/Components/SurveillanceCameraComponent.cs rename to Content.Shared/SurveillanceCamera/Components/SurveillanceCameraComponent.cs index b0d41e52c7..1c5ba8ad17 100644 --- a/Content.Server/SurveillanceCamera/Components/SurveillanceCameraComponent.cs +++ b/Content.Shared/SurveillanceCamera/Components/SurveillanceCameraComponent.cs @@ -1,10 +1,11 @@ using Content.Shared.DeviceNetwork; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -namespace Content.Server.SurveillanceCamera; +namespace Content.Shared.SurveillanceCamera.Components; -[RegisterComponent] -[Access(typeof(SurveillanceCameraSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedSurveillanceCameraSystem))] public sealed partial class SurveillanceCameraComponent : Component { // List of active viewers. This is for bookkeeping purposes, @@ -24,23 +25,20 @@ public sealed partial class SurveillanceCameraComponent : Component // If this camera is active or not. Deactivating a camera // will not allow it to obtain any new viewers. - [ViewVariables] - public bool Active { get; set; } = true; + [DataField] + public bool Active = true; // This one isn't easy to deal with. Will require a UI // to change/set this so mapping these in isn't // the most terrible thing possible. - [ViewVariables(VVAccess.ReadWrite)] [DataField("id")] - public string CameraId { get; set; } = "camera"; + public string CameraId = "camera"; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("nameSet")] - public bool NameSet { get; set; } + [DataField, AutoNetworkedField] + public bool NameSet; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("networkSet")] - public bool NetworkSet { get; set; } + [DataField, AutoNetworkedField] + public bool NetworkSet; // This has to be device network frequency prototypes. [DataField("setupAvailableNetworks")] diff --git a/Content.Shared/SurveillanceCamera/SharedSurveillanceCameraSystem.cs b/Content.Shared/SurveillanceCamera/SharedSurveillanceCameraSystem.cs index e76d5516e8..b6743669e9 100644 --- a/Content.Shared/SurveillanceCamera/SharedSurveillanceCameraSystem.cs +++ b/Content.Shared/SurveillanceCamera/SharedSurveillanceCameraSystem.cs @@ -1,8 +1,56 @@ -using Robust.Shared.GameStates; +using Content.Shared.Emp; +using Content.Shared.SurveillanceCamera.Components; +using Content.Shared.Verbs; using Robust.Shared.Serialization; namespace Content.Shared.SurveillanceCamera; +public abstract class SharedSurveillanceCameraSystem : EntitySystem +{ + public override void Initialize() + { + SubscribeLocalEvent>(AddVerbs); + SubscribeLocalEvent(OnEmpPulse); + SubscribeLocalEvent(OnEmpDisabledRemoved); + } + + private void AddVerbs(EntityUid uid, SurveillanceCameraComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanComplexInteract) + return; + + if (component.NameSet && component.NetworkSet) + return; + + AlternativeVerb verb = new() + { + Text = Loc.GetString("surveillance-camera-setup"), + Act = () => OpenSetupInterface(uid, args.User, component) + }; + args.Verbs.Add(verb); + } + + private void OnEmpPulse(EntityUid uid, SurveillanceCameraComponent component, ref EmpPulseEvent args) + { + if (component.Active) + { + args.Affected = true; + args.Disabled = true; + SetActive(uid, false); + } + } + + private void OnEmpDisabledRemoved(EntityUid uid, SurveillanceCameraComponent component, ref EmpDisabledRemovedEvent args) + { + SetActive(uid, true); + } + + // TODO: predict the rest of the server side system + public virtual void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null) { } + + protected virtual void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null) { } +} + [Serializable, NetSerializable] public enum SurveillanceCameraVisualsKey : byte { diff --git a/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs index 136c4474a2..50fb7a25e4 100644 --- a/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs +++ b/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs @@ -6,7 +6,6 @@ namespace Content.Shared.Trigger.Systems; public sealed class EmpOnTriggerSystem : EntitySystem { [Dependency] private readonly SharedEmpSystem _emp = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { @@ -25,7 +24,7 @@ public sealed class EmpOnTriggerSystem : EntitySystem if (target == null) return; - _emp.EmpPulse(_transform.GetMapCoordinates(target.Value), ent.Comp.Range, ent.Comp.EnergyConsumption, (float)ent.Comp.DisableDuration.TotalSeconds); + _emp.EmpPulse(Transform(target.Value).Coordinates, ent.Comp.Range, ent.Comp.EnergyConsumption, ent.Comp.DisableDuration, args.User); args.Handled = true; } } diff --git a/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs b/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs index 141183873d..610f4d0efe 100644 --- a/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs +++ b/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs @@ -1,19 +1,19 @@ -using Content.Shared.Emag.Components; -using Robust.Shared.Prototypes; using System.Linq; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.Advertise.Components; using Content.Shared.Advertise.Systems; using Content.Shared.DoAfter; +using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; +using Content.Shared.Emp; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Power.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.GameStates; -using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -41,6 +41,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem SubscribeLocalEvent(OnVendingGetState); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnRestockDoAfter); SubscribeLocalEvent(OnAfterInteract); @@ -83,6 +84,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem EjectEnd = component.EjectEnd, DenyEnd = component.DenyEnd, DispenseOnHitEnd = component.DispenseOnHitEnd, + Broken = component.Broken, }; } @@ -145,6 +147,16 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem RestockInventoryFromPrototype(uid, component, component.InitialStockQuality); } + private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) + { + if (!ent.Comp.Broken && _receiver.IsPowered(ent.Owner)) + { + args.Affected = true; + args.Disabled = true; + ent.Comp.NextEmpEject = Timing.CurTime; + } + } + protected virtual void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = null, bool forceEject = false) { } /// diff --git a/Content.Shared/VendingMachines/VendingMachineComponent.cs b/Content.Shared/VendingMachines/VendingMachineComponent.cs index 6a9d650898..29ff3c654a 100644 --- a/Content.Shared/VendingMachines/VendingMachineComponent.cs +++ b/Content.Shared/VendingMachines/VendingMachineComponent.cs @@ -67,6 +67,7 @@ namespace Content.Shared.VendingMachines public string? NextItemToEject; + [DataField] public bool Broken; /// @@ -300,5 +301,7 @@ namespace Content.Shared.VendingMachines public TimeSpan? DenyEnd; public TimeSpan? DispenseOnHitEnd; + + public bool Broken; } } diff --git a/Resources/Prototypes/Entities/Effects/emp_effects.yml b/Resources/Prototypes/Entities/Effects/emp_effects.yml index d1096b85f5..6919f776f2 100644 --- a/Resources/Prototypes/Entities/Effects/emp_effects.yml +++ b/Resources/Prototypes/Entities/Effects/emp_effects.yml @@ -8,17 +8,14 @@ drawdepth: Effects noRot: true layers: - - shader: unshaded - map: ["enum.EffectLayers.Unshaded"] - sprite: Effects/emp.rsi - state: emp_pulse + - shader: unshaded + map: ["enum.EffectLayers.Unshaded"] + sprite: Effects/emp.rsi + state: emp_pulse - type: EffectVisuals - type: Tag tags: - - HideContextMenu - - type: EmitSoundOnSpawn - sound: - path: /Audio/Effects/Lightning/lightningbolt.ogg + - HideContextMenu - type: AnimationPlayer - type: entity @@ -31,12 +28,12 @@ drawdepth: Effects noRot: true layers: - - shader: unshaded - map: ["enum.EffectLayers.Unshaded"] - sprite: Effects/emp.rsi - state: emp_disable + - shader: unshaded + map: ["enum.EffectLayers.Unshaded"] + sprite: Effects/emp.rsi + state: emp_disable - type: EffectVisuals - type: Tag tags: - - HideContextMenu + - HideContextMenu - type: AnimationPlayer -- 2.51.2