mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-15 03:31:30 +01:00
Station alert levels (#8226)
This commit is contained in:
46
Content.Server/AlertLevel/AlertLevelComponent.cs
Normal file
46
Content.Server/AlertLevel/AlertLevelComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Content.Server/AlertLevel/AlertLevelDisplaySystem.cs
Normal file
35
Content.Server/AlertLevel/AlertLevelDisplaySystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Content.Server/AlertLevel/AlertLevelPrototype.cs
Normal file
61
Content.Server/AlertLevel/AlertLevelPrototype.cs
Normal 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;
|
||||
}
|
||||
|
||||
158
Content.Server/AlertLevel/AlertLevelSystem.cs
Normal file
158
Content.Server/AlertLevel/AlertLevelSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user