Station alert levels (#8226)

This commit is contained in:
Flipp Syder
2022-05-17 21:05:31 -07:00
committed by GitHub
parent 2697bbf8c7
commit dcdda39048
21 changed files with 566 additions and 2 deletions

View File

@@ -0,0 +1,46 @@
using System.Linq;
using Content.Shared.AlertLevel;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Utility;
namespace Content.Client.AlertLevel;
public sealed class AlertLevelDisplaySystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AlertLevelDisplayComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnAppearanceChange(EntityUid uid, AlertLevelDisplayComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
{
return;
}
if (!args.Sprite.LayerMapTryGet(AlertLevelDisplay.Layer, out _))
{
var layer = args.Sprite.AddLayer(new RSI.StateId(component.AlertVisuals.Values.First()));
args.Sprite.LayerMapSet(AlertLevelDisplay.Layer, layer);
}
if (!args.AppearanceData.TryGetValue(AlertLevelDisplay.CurrentLevel, out var level))
{
args.Sprite.LayerSetState(AlertLevelDisplay.Layer, new RSI.StateId(component.AlertVisuals.Values.First()));
return;
}
if (component.AlertVisuals.TryGetValue((string) level, out var visual))
{
args.Sprite.LayerSetState(AlertLevelDisplay.Layer, new RSI.StateId(visual));
}
else
{
args.Sprite.LayerSetState(AlertLevelDisplay.Layer, new RSI.StateId(component.AlertVisuals.Values.First()));
}
}
}

View File

@@ -19,6 +19,10 @@ namespace Content.Client.Communications.UI
public bool CountdownStarted { get; private set; }
public bool AlertLevelSelectable { get; private set; }
public string CurrentLevel { get; private set; } = default!;
public int Countdown => _expectedCountdownTime == null
? 0 : Math.Max((int)_expectedCountdownTime.Value.Subtract(_gameTiming.CurTime).TotalSeconds, 0);
private TimeSpan? _expectedCountdownTime;
@@ -36,6 +40,15 @@ namespace Content.Client.Communications.UI
_menu.OpenCentered();
}
public void AlertLevelSelected(string level)
{
if (AlertLevelSelectable)
{
CurrentLevel = level;
SendMessage(new CommunicationsConsoleSelectAlertLevelMessage(level));
}
}
public void EmergencyShuttleButtonPressed()
{
if (CountdownStarted)
@@ -71,10 +84,14 @@ namespace Content.Client.Communications.UI
CanCall = commsState.CanCall;
_expectedCountdownTime = commsState.ExpectedCountdownEnd;
CountdownStarted = commsState.CountdownStarted;
AlertLevelSelectable = commsState.AlertLevels != null && !float.IsNaN(commsState.CurrentAlertDelay) && commsState.CurrentAlertDelay <= 0;
CurrentLevel = commsState.CurrentAlert;
if (_menu != null)
{
_menu.UpdateCountdown();
_menu.UpdateAlertLevels(commsState.AlertLevels, CurrentLevel);
_menu.AlertLevelButton.Disabled = !AlertLevelSelectable;
_menu.EmergencyShuttleButton.Disabled = !CanCall;
_menu.AnnounceButton.Disabled = !CanAnnounce;
}

View File

@@ -18,6 +18,7 @@ namespace Content.Client.Communications.UI
public readonly Button AnnounceButton;
public readonly Button EmergencyShuttleButton;
private readonly RichTextLabel _countdownLabel;
public readonly OptionButton AlertLevelButton;
public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
{
@@ -38,6 +39,17 @@ namespace Content.Client.Communications.UI
AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(_messageInput.Text.Trim());
AnnounceButton.Disabled = !owner.CanAnnounce;
AlertLevelButton = new OptionButton();
AlertLevelButton.OnItemSelected += args =>
{
var metadata = AlertLevelButton.GetItemMetadata(args.Id);
if (metadata != null && metadata is string cast)
{
Owner.AlertLevelSelected(cast);
}
};
AlertLevelButton.Disabled = !owner.AlertLevelSelectable;
_countdownLabel = new RichTextLabel(){MinSize = new Vector2(0, 200)};
EmergencyShuttleButton = new Button();
EmergencyShuttleButton.OnPressed += (_) => Owner.EmergencyShuttleButtonPressed();
@@ -52,6 +64,7 @@ namespace Content.Client.Communications.UI
vbox.AddChild(_messageInput);
vbox.AddChild(new Control(){MinSize = new Vector2(0,10), HorizontalExpand = true});
vbox.AddChild(AnnounceButton);
vbox.AddChild(AlertLevelButton);
vbox.AddChild(new Control(){MinSize = new Vector2(0,10), HorizontalExpand = true});
vbox.AddChild(_countdownLabel);
vbox.AddChild(EmergencyShuttleButton);
@@ -72,6 +85,34 @@ namespace Content.Client.Communications.UI
Timer.SpawnRepeating(1000, UpdateCountdown, _timerCancelTokenSource.Token);
}
// The current alert could make levels unselectable, so we need to ensure that the UI reacts properly.
// If the current alert is unselectable, the only item in the alerts list will be
// the current alert. Otherwise, it will be the list of alerts, with the current alert
// selected.
public void UpdateAlertLevels(List<string>? alerts, string currentAlert)
{
AlertLevelButton.Clear();
if (alerts == null)
{
AlertLevelButton.AddItem(Loc.GetString($"alert-level-{currentAlert}"));
AlertLevelButton.SetItemMetadata(AlertLevelButton.ItemCount - 1, currentAlert);
}
else
{
foreach (var alert in alerts)
{
AlertLevelButton.AddItem(Loc.GetString($"alert-level-{alert}"));
AlertLevelButton.SetItemMetadata(AlertLevelButton.ItemCount - 1, alert);
if (alert == currentAlert)
{
AlertLevelButton.Select(AlertLevelButton.ItemCount - 1);
}
}
}
}
public void UpdateCountdown()
{
if (!Owner.CountdownStarted)

View File

@@ -106,6 +106,7 @@ namespace Content.Client.Entry
prototypes.RegisterIgnore("instantSpell");
prototypes.RegisterIgnore("roundAnnouncement");
prototypes.RegisterIgnore("wireLayout");
prototypes.RegisterIgnore("alertLevels");
prototypes.RegisterIgnore("nukeopsRole");
ClientContentIoC.Register();

View File

@@ -0,0 +1,46 @@
namespace Content.Server.AlertLevel;
/// <summary>
/// Alert level component. This is the component given to a station to
/// signify its alert level state.
/// </summary>
[RegisterComponent]
public sealed class AlertLevelComponent : Component
{
/// <summary>
/// The current set of alert levels on the station.
/// </summary>
[ViewVariables]
public AlertLevelPrototype? AlertLevels;
// Once stations are a prototype, this should be used.
[DataField("alertLevelPrototype")]
public string AlertLevelPrototype = default!;
/// <summary>
/// The current level on the station.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public string CurrentLevel = string.Empty;
[ViewVariables] public const float Delay = 300;
[ViewVariables] public float CurrentDelay = 0;
[ViewVariables] public bool ActiveDelay;
/// <summary>
/// If the level can be selected on the station.
/// </summary>
[ViewVariables]
public bool IsSelectable
{
get
{
if (AlertLevels == null
|| !AlertLevels.Levels.TryGetValue(CurrentLevel, out var level))
{
return false;
}
return level.Selectable && !level.DisableSelection;
}
}
}

View File

@@ -0,0 +1,35 @@
using Content.Server.Station.Systems;
using Content.Shared.AlertLevel;
namespace Content.Server.AlertLevel;
public sealed class AlertLevelDisplaySystem : EntitySystem
{
[Dependency] private readonly StationSystem _stationSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertChanged);
SubscribeLocalEvent<AlertLevelDisplayComponent, ComponentInit>(OnDisplayInit);
}
private void OnAlertChanged(AlertLevelChangedEvent args)
{
foreach (var (_, appearance) in EntityManager.EntityQuery<AlertLevelDisplayComponent, AppearanceComponent>())
{
appearance.SetData(AlertLevelDisplay.CurrentLevel, args.AlertLevel);
}
}
private void OnDisplayInit(EntityUid uid, AlertLevelDisplayComponent component, ComponentInit args)
{
if (TryComp(uid, out AppearanceComponent? appearance))
{
var stationUid = _stationSystem.GetOwningStation(uid);
if (stationUid != null && TryComp(stationUid, out AlertLevelComponent? alert))
{
appearance.SetData(AlertLevelDisplay.CurrentLevel, alert.CurrentLevel);
}
}
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Specialized;
using Content.Shared.Sound;
using Robust.Shared.Prototypes;
namespace Content.Server.AlertLevel;
[Prototype("alertLevels")]
public sealed class AlertLevelPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
/// <summary>
/// Dictionary of alert levels. Keyed by string - the string key is the most important
/// part here. Visualizers will use this in order to dictate what alert level to show on
/// client side sprites, and localization uses each key to dictate the alert level name.
/// </summary>
[DataField("levels")] public Dictionary<string, AlertLevelDetail> Levels = new();
/// <summary>
/// Default level that the station is on upon initialization.
/// If this isn't in the dictionary, this will default to whatever .First() gives.
/// </summary>
[DataField("defaultLevel")] public string DefaultLevel { get; }= default!;
}
/// <summary>
/// Alert level detail. Does not contain an ID, that is handled by
/// the Levels field in AlertLevelPrototype.
/// </summary>
[DataDefinition]
public sealed class AlertLevelDetail
{
/// <summary>
/// What is announced upon this alert level change. Can be a localized string.
/// </summary>
[DataField("announcement")] public string Announcement { get; } = string.Empty;
/// <summary>
/// Whether this alert level is selectable from a communications console.
/// </summary>
[DataField("selectable")] public bool Selectable { get; } = true;
/// <summary>
/// If this alert level disables user selection while it is active. Beware -
/// setting this while something is selectable will disable selection permanently!
/// This should only apply to entities or gamemodes that auto-select an alert level,
/// such as a nuclear bomb being set to active.
/// </summary>
[DataField("disableSelection")] public bool DisableSelection { get; }
/// <summary>
/// The sound that this alert level will play in-game once selected.
/// </summary>
[DataField("sound")] public SoundSpecifier? Sound { get; }
/// <summary>
/// The color that this alert level will show in-game in chat.
/// </summary>
[DataField("color")] public Color Color { get; } = Color.White;
}

View File

@@ -0,0 +1,158 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Chat.Managers;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared.AlertLevel;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.AlertLevel;
public sealed class AlertLevelSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
// Until stations are a prototype, this is how it's going to have to be.
public const string DefaultAlertLevelSet = "stationAlerts";
public override void Initialize()
{
SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialize);
}
public override void Update(float time)
{
foreach (var station in _stationSystem.Stations)
{
if (!TryComp(station, out AlertLevelComponent? alert))
{
continue;
}
if (alert.CurrentDelay <= 0)
{
if (alert.ActiveDelay)
{
RaiseLocalEvent(new AlertLevelDelayFinishedEvent());
alert.ActiveDelay = false;
}
continue;
}
alert.CurrentDelay--;
}
}
private void OnStationInitialize(StationInitializedEvent args)
{
var alertLevelComponent = AddComp<AlertLevelComponent>(args.Station);
if (!_prototypeManager.TryIndex(DefaultAlertLevelSet, out AlertLevelPrototype? alerts))
{
return;
}
alertLevelComponent.AlertLevels = alerts;
var defaultLevel = alertLevelComponent.AlertLevels.DefaultLevel;
if (string.IsNullOrEmpty(defaultLevel))
{
defaultLevel = alertLevelComponent.AlertLevels.Levels.Keys.First();
}
SetLevel(args.Station, defaultLevel, false, false, true);
}
public float GetAlertLevelDelay(EntityUid station, AlertLevelComponent? alert = null)
{
if (!Resolve(station, ref alert))
{
return float.NaN;
}
return alert.CurrentDelay;
}
/// <summary>
/// Set the alert level based on the station's entity ID.
/// </summary>
/// <param name="station">Station entity UID.</param>
/// <param name="level">Level to change the station's alert level to.</param>
/// <param name="playSound">Play the alert level's sound.</param>
/// <param name="announce">Say the alert level's announcement.</param>
/// <param name="force">Force the alert change. This applies if the alert level is not selectable or not.</param>
public void SetLevel(EntityUid station, string level, bool playSound, bool announce, bool force = false,
MetaDataComponent? dataComponent = null, AlertLevelComponent? component = null)
{
if (!Resolve(station, ref component, ref dataComponent)
|| component.AlertLevels == null
|| !component.AlertLevels.Levels.TryGetValue(level, out var detail)
|| component.CurrentLevel == level)
{
return;
}
if (!force)
{
if (!detail.Selectable
|| component.CurrentDelay > 0)
{
return;
}
component.CurrentDelay = AlertLevelComponent.Delay;
component.ActiveDelay = true;
}
component.CurrentLevel = level;
var stationName = dataComponent.EntityName;
var name = Loc.GetString($"alert-level-{level}").ToLower();
// Announcement text. Is passed into announcementFull.
var announcement = Loc.GetString(detail.Announcement);
// The full announcement to be spat out into chat.
var announcementFull = Loc.GetString("alert-level-announcement", ("name", name), ("announcement", announcement));
var playDefault = false;
if (playSound)
{
if (detail.Sound != null)
{
SoundSystem.Play(Filter.Broadcast(), detail.Sound.GetSound());
}
else
{
playDefault = true;
}
}
if (announce)
{
_chatManager.DispatchStationAnnouncement(announcementFull, playDefaultSound: playDefault,
colorOverride: detail.Color, sender: stationName);
}
RaiseLocalEvent(new AlertLevelChangedEvent(level));
}
}
public sealed class AlertLevelDelayFinishedEvent : EntityEventArgs
{}
public sealed class AlertLevelChangedEvent : EntityEventArgs
{
public string AlertLevel { get; }
public AlertLevelChangedEvent(string alertLevel)
{
AlertLevel = alertLevel;
}
}

View File

@@ -1,5 +1,7 @@
using Content.Server.AlertLevel;
using Content.Server.Atmos.Monitor.Components;
using Content.Server.Power.Components;
using Content.Shared.AlertLevel;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Interaction;
using Content.Shared.Emag.Systems;

View File

@@ -1,9 +1,12 @@
using System.Globalization;
using System.Linq;
using System.Threading;
using Content.Server.Access.Systems;
using Content.Server.AlertLevel;
using Content.Server.Chat.Managers;
using Content.Server.Power.Components;
using Content.Server.RoundEnd;
using Content.Server.Station.Systems;
using Content.Server.UserInterface;
using Content.Shared.Communications;
using Robust.Server.GameObjects;
@@ -12,6 +15,7 @@ using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.Communications
{
// TODO: ECS
[RegisterComponent]
public sealed class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IEntityEventSubscriber
{
@@ -24,6 +28,8 @@ namespace Content.Server.Communications
private bool Powered => !_entities.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered;
private RoundEndSystem RoundEndSystem => EntitySystem.Get<RoundEndSystem>();
private AlertLevelSystem AlertLevelSystem => EntitySystem.Get<AlertLevelSystem>();
private StationSystem StationSystem => EntitySystem.Get<StationSystem>();
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CommunicationsConsoleUiKey.Key);
@@ -41,6 +47,8 @@ namespace Content.Server.Communications
}
_entityManager.EventBus.SubscribeEvent<RoundEndSystemChangedEvent>(EventSource.Local, this, (s) => UpdateBoundInterface());
_entityManager.EventBus.SubscribeEvent<AlertLevelChangedEvent>(EventSource.Local, this, _ => UpdateBoundInterface());
_entityManager.EventBus.SubscribeEvent<AlertLevelDelayFinishedEvent>(EventSource.Local, this, _ => UpdateBoundInterface());
}
protected override void Startup()
@@ -56,7 +64,33 @@ namespace Content.Server.Communications
{
var system = RoundEndSystem;
UserInterface?.SetState(new CommunicationsConsoleInterfaceState(CanAnnounce(), system.CanCall(), system.ExpectedCountdownEnd));
List<string>? levels = null;
string currentLevel = default!;
float currentDelay = 0;
var stationUid = StationSystem.GetOwningStation(Owner);
if (stationUid != null)
{
if (_entityManager.TryGetComponent(stationUid.Value, out AlertLevelComponent? alerts)
&& alerts.AlertLevels != null)
{
if (alerts.IsSelectable)
{
levels = new();
foreach (var (id, detail) in alerts.AlertLevels.Levels)
{
if (detail.Selectable)
{
levels.Add(id);
}
}
}
currentLevel = alerts.CurrentLevel;
currentDelay = AlertLevelSystem.GetAlertLevelDelay(stationUid.Value, alerts);
}
}
UserInterface?.SetState(new CommunicationsConsoleInterfaceState(CanAnnounce(), system.CanCall(), levels, currentLevel, currentDelay, system.ExpectedCountdownEnd));
}
}
@@ -108,6 +142,14 @@ namespace Content.Server.Communications
message += $"\nSent by {author}";
_chatManager.DispatchStationAnnouncement(message, "Communications Console", colorOverride: Color.Gold);
break;
case CommunicationsConsoleSelectAlertLevelMessage alertMsg:
var stationUid = StationSystem.GetOwningStation(Owner);
if (stationUid != null)
{
AlertLevelSystem.SetLevel(stationUid.Value, alertMsg.Level, true, true);
}
break;
}
}

View File

@@ -44,6 +44,9 @@ namespace Content.Server.Nuke
[DataField("alertTime")]
public float AlertSoundTime = 10.0f;
[DataField("alertLevelOnActivate")] public string AlertLevelOnActivate = default!;
[DataField("alertLevelOnDeactivate")] public string AlertLevelOnDeactivate = default!;
[DataField("keypadPressSound")]
public SoundSpecifier KeypadPressSound = new SoundPathSpecifier("/Audio/Machines/Nuke/general_beep.ogg");

View File

@@ -1,7 +1,9 @@
using Content.Server.AlertLevel;
using Content.Server.Chat.Managers;
using Content.Server.Coordinates.Helpers;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Popups;
using Content.Server.Station.Systems;
using Content.Server.UserInterface;
using Content.Shared.Audio;
using Content.Shared.Construction.Components;
@@ -20,6 +22,8 @@ namespace Content.Server.Nuke
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly PopupSystem _popups = default!;
[Dependency] private readonly ExplosionSystem _explosions = default!;
[Dependency] private readonly AlertLevelSystem _alertLevel = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly IChatManager _chat = default!;
public override void Initialize()
@@ -323,6 +327,15 @@ namespace Content.Server.Nuke
if (component.Status == NukeStatus.ARMED)
return;
var stationUid = _stationSystem.GetOwningStation(uid);
// The nuke may not be on a station, so it's more important to just
// let people know that a nuclear bomb was armed in their vicinity instead.
// Otherwise, you could set every station to whatever AlertLevelOnActivate is.
if (stationUid != null)
{
_alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnActivate, true, true, true);
}
// warn a crew
var announcement = Loc.GetString("nuke-component-announcement-armed",
("time", (int) component.RemainingTime));
@@ -347,6 +360,12 @@ namespace Content.Server.Nuke
if (component.Status != NukeStatus.ARMED)
return;
var stationUid = _stationSystem.GetOwningStation(uid);
if (stationUid != null)
{
_alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnDeactivate, true, true, true);
}
// warn a crew
var announcement = Loc.GetString("nuke-component-announcement-unarmed");
var sender = Loc.GetString("nuke-component-announcement-sender");

View File

@@ -0,0 +1,8 @@
namespace Content.Shared.AlertLevel;
[RegisterComponent]
public sealed class AlertLevelDisplayComponent : Component
{
[DataField("alertVisuals")]
public readonly Dictionary<string, string> AlertVisuals = new();
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.Serialization;
namespace Content.Shared.AlertLevel;
[Serializable, NetSerializable]
public enum AlertLevelDisplay
{
CurrentLevel,
Layer
}

View File

@@ -14,13 +14,30 @@ namespace Content.Shared.Communications
public readonly bool CanCall;
public readonly TimeSpan? ExpectedCountdownEnd;
public readonly bool CountdownStarted;
public List<string>? AlertLevels;
public string CurrentAlert;
public float CurrentAlertDelay;
public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, TimeSpan? expectedCountdownEnd = null)
public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List<string>? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd = null)
{
CanAnnounce = canAnnounce;
CanCall = canCall;
ExpectedCountdownEnd = expectedCountdownEnd;
CountdownStarted = expectedCountdownEnd != null;
AlertLevels = alertLevels;
CurrentAlert = currentAlert;
CurrentAlertDelay = currentAlertDelay;
}
}
[Serializable, NetSerializable]
public sealed class CommunicationsConsoleSelectAlertLevelMessage : BoundUserInterfaceMessage
{
public readonly string Level;
public CommunicationsConsoleSelectAlertLevelMessage(string level)
{
Level = level;
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,19 @@
alert-level-announcement = Attention! Station alert level is now {$name}! {$announcement}
alert-level-green = Green
alert-level-green-announcement = It is now safe to return to your workplaces.
alert-level-blue = Blue
alert-level-blue-announcement = There is an unknown threat on the station. Security is allowed to perform random checks. Crewmembers are advised to follow commands issued by any relevant authority.
alert-level-red = Red
alert-level-red-announcement = There is a known threat on the station. Security is allowed to use lethal force if necessary. Crewmembers should find a safe place to shelter in, and are advised to follow any present authorities.
alert-level-violet = Violet
alert-level-violet-announcement = There is a viral threat on the station. Medical staff are advised to isolate crewmembers with any symptoms. Crewmembers are advised to distance themselves from others and perform safety measures to prevent further spread.
alert-level-yellow = Yellow
alert-level-yellow-announcement = There is a structural or atmospheric threat within the station. Engineering staff are advised to immediately respond and perform safety measures. Crewmembers are advised to stay away from the threat, and stay in their workplaces if necessary.
alert-level-delta = Delta
alert-level-delta-announcement = The station is currently under threat of imminent destruction. Crewmembers are advised to listen to heads of staff for more information.

View File

@@ -0,0 +1,29 @@
- type: alertLevels
id: stationAlerts
defaultLevel: green
levels:
green:
announcement: alert-level-green-announcement
color: Green
blue:
announcement: alert-level-blue-announcement
sound: /Audio/Misc/notice1.ogg
color: DodgerBlue
red:
announcement: alert-level-red-announcement
sound: /Audio/Misc/notice1.ogg
color: Red
violet:
announcement: alert-level-violet-announcement
sound: /Audio/Misc/notice1.ogg
color: Violet
yellow:
announcement: alert-level-yellow-announcement
sound: /Audio/Misc/notice1.ogg
color: Yellow
delta:
announcement: alert-level-delta-announcement
selectable: false
sound: /Audio/Misc/delta.ogg
disableSelection: true
color: DarkRed

View File

@@ -38,6 +38,8 @@
whitelist:
components:
- NukeDisk
alertLevelOnActivate: delta
alertLevelOnDeactivate: green
- type: InteractionOutline
- type: ActivatableUI
key: enum.NukeUiKey.Key

View File

@@ -34,6 +34,14 @@
Emagged: fire_emagged
hideOnDepowered: ["fireAlarmState"]
- type: WiresVisualizer
- type: AlertLevelDisplay
alertVisuals:
green: fire_0
blue: fire_1
red: fire_2
violet: fire_1
yellow: fire_1
delta: fire_3
- type: UserInterface
interfaces:
- key: enum.WiresUiKey.Key