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 @@
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;
}
}