using System.Diagnostics.CodeAnalysis; using Content.Shared.Chat; using Content.Shared.Station; using Content.Shared.CCVar; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Shared.AlertLevel; public sealed partial class AlertLevelSystem : EntitySystem { [Dependency] private IConfigurationManager _cfg = default!; [Dependency] private IGameTiming _timing = default!; [Dependency] private INetManager _net = default!; [Dependency] private IPrototypeManager _prototype = default!; [Dependency] private SharedAudioSystem _audio = default!; [Dependency] private SharedChatSystem _chat = default!; [Dependency] private SharedStationSystem _station = default!; private static string _fallbackAlertKey = "alert-level-unknown"; private static string _fallbackAlertName = $"{_fallbackAlertKey}"; private static string _fallbackAlertAnnouncement = $"{_fallbackAlertKey}"; private static string _fallbackAlertInstructions = $"{_fallbackAlertKey}"; public override void Update(float time) { var query = EntityQueryEnumerator(); var curTime = _timing.CurTime; while (query.MoveNext(out var station, out var alertComp)) { if (alertComp.DelayedUntil <= curTime) { // Raise an event so that UIs can refresh. var ev = new AlertLevelDelayFinishedEvent(); RaiseLocalEvent(ref ev); alertComp.DelayedUntil = null; Dirty(station, alertComp); } } } /// /// Can the alert level currently be changed from the communications console? /// This considers both the cooldown and if the current mode prevents changing it, for example when the nuke is armed. /// public bool CanChangeAlertLevel(Entity station) { if (!Resolve(station, ref station.Comp)) return false; return station.Comp.DelayedUntil == null && !station.Comp.IsLevelLocked; } /// /// Gets a list of alert levels that are available on this station and can be selected from the communications console. /// public List> GetSelectableAlertLevels(Entity station) { List> list = new(); if (!Resolve(station, ref station.Comp)) return list; foreach (var levelId in station.Comp.AvailableAlertLevels) { if (!_prototype.Resolve(levelId, out var level)) continue; if (level.Selectable) list.Add(levelId); } return list; } /// /// Gets the current alert level for this station entity. /// public bool TryGetLevel(Entity station, [NotNullWhen(true)] out ProtoId? level) { level = null; if (!Resolve(station, ref station.Comp, false)) return false; level = station.Comp.CurrentAlertLevel; return true; } /// /// Gets the default alert level for this station entity. /// public bool TryGetDefaultLevel(Entity station, [NotNullWhen(true)] out ProtoId? level) { level = null; if (!Resolve(station, ref station.Comp, false)) return false; level = station.Comp.DefaultAlertLevel; return true; } /// /// Gets the remaining cooldown for changing the alert level from the communications console. /// public TimeSpan GetAlertLevelDelay(Entity station) { if (!Resolve(station, ref station.Comp, false)) return TimeSpan.Zero; if (station.Comp.DelayedUntil == null) return TimeSpan.Zero; return station.Comp.DelayedUntil.Value - _timing.CurTime; } /// /// Set the alert level based on the station's entity uid. /// /// Station entity UID. /// Level to change the station's alert level to. /// Play the alert level's sound? /// Announce the alert level change in chat? /// Force the alert change. This applies if the alert level is not selectable. /// Override to decide if the crew will be able to change from this alert level via the console. Will use the value given in the prototype if null. public void SetLevel( Entity station, ProtoId level, bool playSound = true, bool announce = true, bool force = false, bool? locked = null) { if (!Resolve(station, ref station.Comp)) return; if (station.Comp.CurrentAlertLevel == level) return; if (!_prototype.Resolve(level, out var prototype)) return; if (!force) { if (!CanChangeAlertLevel(station) || !prototype.Selectable) return; station.Comp.DelayedUntil = _timing.CurTime + TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.GameAlertLevelChangeDelay)); } station.Comp.CurrentAlertLevel = level; station.Comp.IsLevelLocked = prototype.DisableSelection; if (locked != null) // optional override station.Comp.IsLevelLocked = locked.Value; Dirty(station); var stationName = MetaData(station.Owner).EntityName; // The full announcement to be spat out into chat. var announcementFull = Loc.GetString("alert-level-announcement", ("name", prototype.LocalizedName), ("announcement", AlertLevelAnnouncement(prototype))); var ev = new AlertLevelChangedEvent(station, level); RaiseLocalEvent(ref ev); if (_net.IsClient) return; var playDefault = false; if (playSound) { if (prototype.Sound != null) { var filter = _station.GetInOwningStation(station); _audio.PlayGlobal(prototype.Sound, filter, true); } else { playDefault = true; } } if (announce) { _chat.DispatchStationAnnouncement( station, announcementFull, playDefaultSound: playDefault, colorOverride: prototype.Color, sender: stationName); } } /// /// Returns the name, announcement, and description of this station's alert level. /// public (string Name, string Announcement, string Instructions) AlertLevelData(Entity station) { if (!TryGetDefaultLevel(station, out var level)) { return (Loc.GetString(_fallbackAlertName), Loc.GetString(_fallbackAlertAnnouncement), Loc.GetString(_fallbackAlertInstructions)); } return AlertLevelData(level); } /// /// Returns the name, announcement, and description of the given alert level. /// public (string Name, string Announcement, string Instructions) AlertLevelData(ProtoId? level) { if (!ProtoMan.Resolve(level, out var proto)) { return (Loc.GetString(_fallbackAlertName), Loc.GetString(_fallbackAlertAnnouncement), Loc.GetString(_fallbackAlertInstructions)); } return AlertLevelData(proto); } /// /// Returns the name, announcement, and description of the given alert level. /// public (string Name, string Announcement, string Instructions) AlertLevelData(AlertLevelPrototype level) { return (level.LocalizedName, AlertLevelAnnouncement(level), AlertLevelInstructions(level)); } /// /// Returns the name of this station's current alert level. /// /// Returns the name of the current Alert Level. public string AlertLevelName(Entity station) { return !TryGetDefaultLevel(station, out var level) ? Loc.GetString(_fallbackAlertName) : AlertLevelName(level); } /// /// Returns the name of the given alert level. /// /// Returns the name of the given Alert Level. public string AlertLevelName(ProtoId? level) { return !ProtoMan.Resolve(level, out var proto) ? Loc.GetString(_fallbackAlertName) : proto.LocalizedName; } /// /// Gets the announcement message for the stations' current alert level. /// /// Returns the description of the current Alert Level. public string AlertLevelAnnouncement(Entity station) { return !TryGetDefaultLevel(station, out var level) ? Loc.GetString(_fallbackAlertAnnouncement) : AlertLevelAnnouncement(level); } /// public string AlertLevelAnnouncement(ProtoId? level) { return !ProtoMan.Resolve(level, out var proto) ? Loc.GetString(_fallbackAlertAnnouncement) : AlertLevelAnnouncement(proto); } /// /// Gets the announcement message for the given alert level. /// /// Returns the announcement for the given Alert Level. public string AlertLevelAnnouncement(AlertLevelPrototype level) { return Loc.GetString(level.Announcement ?? _fallbackAlertAnnouncement); } /// /// Gets the instructions for the station's current alert level. /// /// Returns the instructions for the current alert level. public string AlertLevelInstructions(Entity station) { return !TryGetDefaultLevel(station, out var level) ? Loc.GetString(_fallbackAlertInstructions) : AlertLevelInstructions(level); } /// public string AlertLevelInstructions(ProtoId? level) { return !ProtoMan.Resolve(level, out var proto) ? Loc.GetString(_fallbackAlertInstructions) : AlertLevelInstructions(proto); } /// /// Gets the instructions for the given alert level. /// /// Returns the instructions for the given Alert Level. public string AlertLevelInstructions(AlertLevelPrototype level) { return Loc.GetString(level.Instructions ?? _fallbackAlertInstructions); } } /// /// Broadcast event that is raised when the alert level selection delay for a station is over and it can be switched again. /// [ByRefEvent] public record struct AlertLevelDelayFinishedEvent; /// /// Broadcast event that is raised when the a station changes its alert level. /// [ByRefEvent] public record struct AlertLevelChangedEvent(EntityUid Station, ProtoId AlertLevel);