CatStation/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs
Rouden cc2c14f8e4
Device Network refactor part 1: API improvements (#44346)
* Refactor DeviceNetwork

* Singleton

* NetworkPayloads refactor

* Reorganize DeviceNetworkSystem

* Surveillance cameras

* Fix code

* Fix build

* Fix test fail by rewriting entire singleton logic

* Properly predict examine

* Update DeviceNetworkSystem.API.cs

* Update EntityTest.cs

* Update DeviceNetworkSystem.cs

* Alright I'll do this expensive check fine

* Fix disposals debug assert

* Implement device network static events support

* Use frozen dictionaries for performance

* Update SharedDeviceNetworkSystem.Payloads.cs

* Parallel payload handling support

* Fix some spagetti-code

* Optimize NetworkPayload cancellation

* Parallel payloads are now cancellable

* Fix screens

* Fix robotics console

* Allow new methods for shared

* Mail fix

* Turret fix

* Fix network system method usage + signal and borg fix

* finally fix atmos devices OH MY GOOOD

* Remove stupid parallelism from fax

* Fix surveiollance cameras

* Working on a single thing for 2 days straight award

* Fix the test?

* Update units.yml

* Add IDevice interface because why not

* Add more device network suffixes

* Fix camera router failing test

* yaml linter peak

* Improve docs for DeviceNetworkHandlers

* fix build

* Update CartridgeLoaderSystem.Relay.cs

* Remove parallel system and EntitySystem post init

* Add more docs and also events

* Remove unnecessary caching

* Update DevicePayloadComponentSystem.cs

* Back to entity events

* Implement DeviceData

* Remove serializable from payloads

* Update DeviceData.cs

* Add a prototype for a singleton

* Fix tests

* Fix air alarms

* Update DeviceNetworkSystem.cs

* Fix cameras

* Make camera router event-less

* Fix atmos sync payloads, add more docs

* Remove singleton + instant packets

* oops remove public access

* Fix tests?

* Why the hell overload effect makes puddle sparks

* Apparently it was redundant

* Update gates.yml

* Finish documentation

* Review

* Mark relay properties as obsolete

* Struct payloads support (#blazinglyfastmemorysafe)

* Update DeviceNetworkTest.cs

* Make device structs stuff better

* Move address prefix to DeviceNetworkComponent

* Locale fixes

* Fix tests

* Doesn't know boolean logic award

* Update AtmosAlarmableSystem.cs

* Fix address predicted examine

* Partial fix to air alarmables

* Fix deployable turrets UI

* Fix firelocks, maybe

* Apparently Activatable UI was enough lol

* Fix surveillance cameras

* Rename `QueuePacket` into `SendPacket`

* Simplify DeviceData as much as possible

* Update DeviceData.cs

* Update FaxMachineComponent.cs

* Remove DeviceData because its stupid

* Update ScreenSystem.cs

* Fix build (camera map broken still)

* Fix map

* fix usings

* Review changes

* Build fix
2026-08-31 19:42:56 +00:00

281 lines
8.9 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Atmos.Monitor.Components;
using Content.Server.Atmos.Monitor.Payloads;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.Atmos.Monitor;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power;
using Content.Shared.Tag;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Content.Shared.DeviceNetwork.Components;
namespace Content.Server.Atmos.Monitor.Systems;
public sealed partial class AtmosAlarmableSystem : EntitySystem
{
[Dependency] private AppearanceSystem _appearance = default!;
[Dependency] private AudioSystem _audioSystem = default!;
[Dependency] private DeviceNetworkSystem _deviceNet = default!;
[Dependency] private AtmosDeviceNetworkSystem _atmosDevNetSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosAlarmableComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AtmosAlarmableComponent, PowerChangedEvent>(OnPowerChange);
}
private void OnMapInit(EntityUid uid, AtmosAlarmableComponent component, MapInitEvent args)
{
TryUpdateAlert(
uid,
TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosAlarmType.Normal,
component,
false);
}
private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, ref PowerChangedEvent args)
{
if (!args.Powered)
{
Reset(uid, component);
}
else
{
// sussy
_atmosDevNetSystem.Register(uid, null);
_atmosDevNetSystem.Sync(uid, null);
TryUpdateAlert(
uid,
TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosAlarmType.Normal,
component,
false);
}
}
[SubscribeLocalEvent]
private void OnAlarmPayload(Entity<AtmosAlarmableComponent> ent, ref DeviceNetworkPacketEvent<AtmosAlarmPayload> args)
{
var payload = args.Data;
if (!CheckTags(ent, payload))
return;
var (uid, component) = ent;
var isValid = payload.TrippedThresholds == null
|| component.MonitorAlertTypes == AtmosMonitorThresholdTypeFlags.None
|| (payload.TrippedThresholds & component.MonitorAlertTypes) != 0;
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
{
if (!isValid)
return;
component.NetworkAlarmStates.Add(args.SenderAddress, payload.Type);
}
else
{
// This is because if the alert is no longer valid,
// it may mean that the threshold we need to look at has
// been removed from the threshold types passed:
// basically, we need to reset this state to normal here.
component.NetworkAlarmStates[args.SenderAddress] = isValid ? payload.Type : AtmosAlarmType.Normal;
}
if (!TryGetHighestAlert(uid, out var netMax, component))
{
netMax = AtmosAlarmType.Normal;
}
TryUpdateAlert(uid, netMax.Value, component);
}
[SubscribeLocalEvent]
private void OnResetAllPayload(Entity<AtmosAlarmableComponent> ent, ref DeviceNetworkPacketEvent<AtmosAlarmableResetAllPayload> args)
{
if (!CheckTags(ent, args.Data))
return;
Reset(ent.Owner, ent.Comp);
}
[SubscribeLocalEvent]
private void OnSyncAlertsPayload(Entity<AtmosAlarmableComponent> ent, ref DeviceNetworkPacketEvent<AtmosAlarmableSyncAlertsPayload> args)
{
if (!CheckTags(ent, args.Data))
return;
foreach (var (key, alarm) in args.Data.AlarmStates)
{
ent.Comp.NetworkAlarmStates.TryAdd(key, alarm);
ent.Comp.NetworkAlarmStates[key] = alarm;
}
if (TryGetHighestAlert(ent, out var maxAlert, ent.Comp))
{
TryUpdateAlert(ent, maxAlert.Value, ent.Comp);
}
}
private static bool CheckTags<T>(Entity<AtmosAlarmableComponent> ent, T payload) where T : IAtmosAlarmableSourcePayload
{
return !ent.Comp.IgnoreAlarms && payload.Source.Any(source => ent.Comp.SyncWithTags.Contains(source));
}
private void TryUpdateAlert(EntityUid uid, AtmosAlarmType type, AtmosAlarmableComponent alarmable, bool sync = true)
{
if (alarmable.LastAlarmState == type)
{
return;
}
if (sync)
{
SyncAlertsToNetwork(uid, null, alarmable);
}
alarmable.LastAlarmState = type;
UpdateAppearance(uid, type);
PlayAlertSound(uid, type, alarmable);
RaiseLocalEvent(uid, new AtmosAlarmEvent(type), true);
}
public void SyncAlertsToNetwork(EntityUid uid, string? address = null, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
{
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
{
return;
}
var payload = new AtmosAlarmableSyncAlertsPayload
{
AlarmStates = alarmable.NetworkAlarmStates,
Source = tags.Tags,
};
_deviceNet.SendPacket(uid, address, ref payload);
}
/// <summary>
/// Forces this alarmable to have a specific alert. This will not be reset until the alarmable
/// is manually reset. This will store the alarmable as a device in its network states.
/// </summary>
/// <param name="uid"></param>
/// <param name="alarmType"></param>
/// <param name="alarmable"></param>
public void ForceAlert(EntityUid uid, AtmosAlarmType alarmType,
AtmosAlarmableComponent? alarmable = null, DeviceNetworkComponent? devNet = null, TagComponent? tags = null)
{
if (!Resolve(uid, ref alarmable, ref devNet, ref tags))
{
return;
}
TryUpdateAlert(uid, alarmType, alarmable, false);
if (alarmable.ReceiveOnly)
{
return;
}
if (!alarmable.NetworkAlarmStates.TryAdd(devNet.Address, alarmType))
{
alarmable.NetworkAlarmStates[devNet.Address] = alarmType;
}
var payload = new AtmosAlarmPayload
{
Type = alarmType,
Source = tags.Tags,
};
_deviceNet.SendPacket(uid, null, ref payload);
}
/// <summary>
/// Resets the state of this alarmable to normal.
/// </summary>
public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref alarmable, ref tags, ref device, false) || alarmable.LastAlarmState == AtmosAlarmType.Normal)
{
return;
}
if (alarmable.NetworkAlarmStates.ContainsKey(device.Address))
alarmable.NetworkAlarmStates[device.Address] = AtmosAlarmType.Normal;
if (!alarmable.ReceiveOnly)
{
var payload = new AtmosMonitorResetPayload();
_deviceNet.SendPacket(uid, null, ref payload);
}
TryUpdateAlert(uid, AtmosAlarmType.Normal, alarmable);
alarmable.NetworkAlarmStates.Clear();
}
public void ResetAllOnNetwork(EntityUid uid, AtmosAlarmableComponent? alarmable = null)
{
if (!Resolve(uid, ref alarmable) || alarmable.ReceiveOnly)
{
return;
}
Reset(uid, alarmable);
}
/// <summary>
/// Tries to get the highest possible alert stored in this alarm.
/// </summary>
/// <param name="uid"></param>
/// <param name="alarm"></param>
/// <param name="alarmable"></param>
/// <returns></returns>
public bool TryGetHighestAlert(EntityUid uid, [NotNullWhen(true)] out AtmosAlarmType? alarm,
AtmosAlarmableComponent? alarmable = null)
{
alarm = null;
if (!Resolve(uid, ref alarmable, false))
{
return false;
}
foreach (var alarmState in alarmable.NetworkAlarmStates.Values)
{
alarm = alarm == null || alarm < alarmState ? alarmState : alarm;
}
return alarm != null;
}
private void PlayAlertSound(EntityUid uid, AtmosAlarmType alarm, AtmosAlarmableComponent alarmable)
{
if (alarm == AtmosAlarmType.Danger)
{
var audioParams = alarmable.AlarmSound?.Params ?? AudioParams.Default;
audioParams = audioParams.AddVolume(alarmable.AlarmVolume);
_audioSystem.PlayPvs(alarmable.AlarmSound, uid, audioParams);
}
}
private void UpdateAppearance(EntityUid uid, AtmosAlarmType alarm)
{
_appearance.SetData(uid, AtmosMonitorVisuals.AlarmType, alarm);
}
}
public sealed class AtmosAlarmEvent : EntityEventArgs
{
public AtmosAlarmType AlarmType { get; }
public AtmosAlarmEvent(AtmosAlarmType netMax)
{
AlarmType = netMax;
}
}