diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleComponent.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleComponent.cs
index c2d7124194..3eef6bc0c5 100644
--- a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleComponent.cs
+++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleComponent.cs
@@ -1,8 +1,10 @@
using Content.Shared.Medical.SuitSensor;
+using Robust.Shared.Audio; // DeltaV
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; // DeltaV
namespace Content.Server.Medical.CrewMonitoring;
-[RegisterComponent]
+[RegisterComponent, AutoGenerateComponentPause] // DeltaV - add AutoGenerateComponentPause
[Access(typeof(CrewMonitoringConsoleSystem))]
public sealed partial class CrewMonitoringConsoleComponent : Component
{
@@ -16,4 +18,35 @@ public sealed partial class CrewMonitoringConsoleComponent : Component
///
[DataField("sensorTimeout"), ViewVariables(VVAccess.ReadWrite)]
public float SensorTimeout = 10f;
+
+ // DeltaV - start of alert system code
+ ///
+ /// Should the component beep if someone goes critical or dies
+ ///
+ [DataField]
+ public bool AlertsEnabled = true;
+
+ ///
+ /// Track sensors that have triggered the crew member critical alert.
+ ///
+ public HashSet AlertedSensors = [];
+
+ ///
+ /// Timestamp of the next possible alert (alert cooldown)
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
+ public TimeSpan NextAlert;
+
+ ///
+ /// Time between alerts
+ ///
+ [DataField]
+ public TimeSpan AlertCooldown = TimeSpan.FromSeconds(15);
+
+ ///
+ /// Alert sound that is played when a crew member goes into critical / dies.
+ ///
+ [DataField]
+ public SoundSpecifier AlertSound = new SoundPathSpecifier("/Audio/_DV/Medical/CrewMonitoring/crew_alert.ogg");
+ // DeltaV - end of alert system code
}
diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs
index a53df6dbae..f5176fe5ba 100644
--- a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs
+++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs
@@ -1,11 +1,15 @@
using System.Linq;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems;
+using Content.Server.Power.EntitySystems; // DeltaV
using Content.Server.PowerCell;
using Content.Shared.Medical.CrewMonitoring;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Pinpointer;
using Robust.Server.GameObjects;
+using Robust.Shared.Audio; // DeltaV
+using Robust.Shared.Audio.Systems; // DeltaV
+using Robust.Shared.Timing; // DeltaV
namespace Content.Server.Medical.CrewMonitoring;
@@ -13,6 +17,8 @@ public sealed class CrewMonitoringConsoleSystem : EntitySystem
{
[Dependency] private readonly PowerCellSystem _cell = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!; // DeltaV
+ [Dependency] private readonly IGameTiming _timing = default!; // DeltaV
public override void Initialize()
{
@@ -43,6 +49,48 @@ public sealed class CrewMonitoringConsoleSystem : EntitySystem
component.ConnectedSensors = sensorStatus;
UpdateUserInterface(uid, component);
+
+ // DeltaV - start of alert system code
+ if (!component.AlertsEnabled)
+ return;
+
+ // station power (for the machine version)
+ if (!this.IsPowered(uid, EntityManager))
+ return;
+
+ // cell power (for the handheld)
+ if (!_cell.HasActivatableCharge(uid))
+ return;
+
+ foreach (var (sensorId, status) in sensorStatus)
+ {
+ // DamagePercentage above 1f is considered critical. It is null when sensor vitals are off.
+ var isCritical = status.DamagePercentage is >= 1f;
+
+ // Skip crew members that we have already alerted about
+ if (component.AlertedSensors.Contains(sensorId))
+ {
+ if (status.IsAlive && !isCritical)
+ component.AlertedSensors.Remove(sensorId);
+ continue;
+ }
+
+ if (!status.IsAlive || isCritical)
+ {
+ if (_timing.CurTime >= component.NextAlert)
+ {
+ var audioParams = AudioParams.Default.WithVolume(-2f).WithMaxDistance(4f);
+ _audio.PlayPvs(component.AlertSound, uid, audioParams);
+ component.NextAlert = _timing.CurTime + component.AlertCooldown;
+ }
+
+ // We are doing this outside the cooldown check to avoid "alert queues"
+ // If two people die at the same time and remain dead for longer, we want to alert once for both people
+ // instead of alerting once for the first one, waiting the cooldown, and then alerting again for the second one.
+ component.AlertedSensors.Add(sensorId);
+ }
+ }
+ // DeltaV - end of alert system code
}
private void OnUIOpened(EntityUid uid, CrewMonitoringConsoleComponent component, BoundUIOpenedEvent args)
diff --git a/Resources/Audio/_DV/Medical/CrewMonitoring/crew_alert.ogg b/Resources/Audio/_DV/Medical/CrewMonitoring/crew_alert.ogg
new file mode 100644
index 0000000000..d7474c8fc2
Binary files /dev/null and b/Resources/Audio/_DV/Medical/CrewMonitoring/crew_alert.ogg differ
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
index a2887d1cba..6a7b505fe2 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
@@ -61,6 +61,8 @@
price: 750
- type: Tag # DeltaV - Let it be put in storage implants by removing HighRiskItem
tags: []
+ - type: CrewMonitoringConsole # DeltaV - disable crew crit / dead beeping alerts
+ alertsEnabled: false
- type: entity
id: SpyCrewMonitorEmpty
@@ -85,6 +87,8 @@
sprite: Objects/Specific/Medical/syndihandheldcrewmonitor.rsi
- type: PowerCellDraw
useRate: 0 # DeltaV - Changed to zero with the removal of the microreactor in observations kit
+ - type: CrewMonitoringConsole # DeltaV - disable crew crit / dead beeping alerts
+ alertsEnabled: false
- type: entity
id: SyndiCrewMonitorEmpty