diff --git a/Content.Client/Alerts/ClientAlertsComponent.cs b/Content.Client/Alerts/ClientAlertsComponent.cs deleted file mode 100644 index 385135589c..0000000000 --- a/Content.Client/Alerts/ClientAlertsComponent.cs +++ /dev/null @@ -1,226 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Content.Client.Alerts.UI; -using Content.Shared.Alert; -using Robust.Client.GameObjects; -using Robust.Client.Player; -using Robust.Client.UserInterface; -using Robust.Shared.GameObjects; -using Robust.Shared.Input; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Prototypes; -using Robust.Shared.ViewVariables; -using static Robust.Client.UserInterface.Controls.BaseButton; - -namespace Content.Client.Alerts -{ - /// - [RegisterComponent] - [ComponentReference(typeof(SharedAlertsComponent))] - public sealed class ClientAlertsComponent : SharedAlertsComponent - { - [Dependency] private readonly IPlayerManager _playerManager = default!; - - private AlertsUI? _ui; - private AlertOrderPrototype? _alertOrder; - - [ViewVariables] - private readonly Dictionary _alertControls - = new(); - - /// - /// Allows calculating if we need to act due to this component being controlled by the current mob - /// - [ViewVariables] - private bool CurrentlyControlled => _playerManager.LocalPlayer != null && _playerManager.LocalPlayer.ControlledEntity == Owner; - - protected override void Shutdown() - { - base.Shutdown(); - PlayerDetached(); - } - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not AlertsComponentState) - { - return; - } - - UpdateAlertsControls(); - } - - public void PlayerAttached() - { - if (!CurrentlyControlled || _ui != null) - { - return; - } - - _alertOrder = IoCManager.Resolve().EnumeratePrototypes().FirstOrDefault(); - if (_alertOrder == null) - { - Logger.ErrorS("alert", "no alertOrder prototype found, alerts will be in random order"); - } - - _ui = new AlertsUI(); - IoCManager.Resolve().StateRoot.AddChild(_ui); - - UpdateAlertsControls(); - } - - public void PlayerDetached() - { - foreach (var alertControl in _alertControls.Values) - { - alertControl.OnPressed -= AlertControlOnPressed; - } - - if (_ui != null) - { - IoCManager.Resolve().StateRoot.RemoveChild(_ui); - _ui = null; - } - _alertControls.Clear(); - } - - /// - /// Updates the displayed alerts based on current state of Alerts, performing - /// a diff to ensure we only change what's changed (this avoids active tooltips disappearing any - /// time state changes) - /// - private void UpdateAlertsControls() - { - if (!CurrentlyControlled || _ui == null) - { - return; - } - - // remove any controls with keys no longer present - var toRemove = new List(); - foreach (var existingKey in _alertControls.Keys) - { - if (!IsShowingAlert(existingKey)) - { - toRemove.Add(existingKey); - } - } - foreach (var alertKeyToRemove in toRemove) - { - _alertControls.Remove(alertKeyToRemove, out var control); - if (control == null) return; - _ui.AlertContainer.Children.Remove(control); - } - - // now we know that alertControls contains alerts that should still exist but - // may need to updated, - // also there may be some new alerts we need to show. - // further, we need to ensure they are ordered w.r.t their configured order - foreach (var (alertKey, alertState) in EnumerateAlertStates()) - { - if (!alertKey.AlertType.HasValue) - { - Logger.WarningS("alert", "found alertkey without alerttype," + - " alert keys should never be stored without an alerttype set: {0}", alertKey); - continue; - } - var alertType = alertKey.AlertType.Value; - if (!AlertManager.TryGet(alertType, out var newAlert)) - { - Logger.ErrorS("alert", "Unrecognized alertType {0}", alertType); - continue; - } - - if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) && - existingAlertControl.Alert.AlertType == newAlert.AlertType) - { - // key is the same, simply update the existing control severity / cooldown - existingAlertControl.SetSeverity(alertState.Severity); - existingAlertControl.Cooldown = alertState.Cooldown; - } - else - { - if (existingAlertControl != null) - { - _ui.AlertContainer.Children.Remove(existingAlertControl); - } - - // this is a new alert + alert key or just a different alert with the same - // key, create the control and add it in the appropriate order - var newAlertControl = CreateAlertControl(newAlert, alertState); - if (_alertOrder != null) - { - var added = false; - foreach (var alertControl in _ui.AlertContainer.Children) - { - if (_alertOrder.Compare(newAlert, ((AlertControl) alertControl).Alert) < 0) - { - var idx = alertControl.GetPositionInParent(); - _ui.AlertContainer.Children.Add(newAlertControl); - newAlertControl.SetPositionInParent(idx); - added = true; - break; - } - } - - if (!added) - { - _ui.AlertContainer.Children.Add(newAlertControl); - } - } - else - { - _ui.AlertContainer.Children.Add(newAlertControl); - } - - _alertControls[newAlert.AlertKey] = newAlertControl; - } - } - } - - private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState) - { - var alertControl = new AlertControl(alert, alertState.Severity) - { - Cooldown = alertState.Cooldown - }; - alertControl.OnPressed += AlertControlOnPressed; - return alertControl; - } - - private void AlertControlOnPressed(ButtonEventArgs args) - { - if (args.Button is not AlertControl control) - { - return; - } - - AlertPressed(args, control); - } - - private void AlertPressed(ButtonEventArgs args, AlertControl alert) - { - if (args.Event.Function != EngineKeyFunctions.UIClick) - { - return; - } - -#pragma warning disable 618 - SendNetworkMessage(new ClickAlertMessage(alert.Alert.AlertType)); -#pragma warning restore 618 - } - - protected override void AfterShowAlert() - { - UpdateAlertsControls(); - } - - protected override void AfterClearAlert() - { - UpdateAlertsControls(); - } - } -} diff --git a/Content.Client/Alerts/ClientAlertsSystem.cs b/Content.Client/Alerts/ClientAlertsSystem.cs index 19d266fbfe..0c189963e3 100644 --- a/Content.Client/Alerts/ClientAlertsSystem.cs +++ b/Content.Client/Alerts/ClientAlertsSystem.cs @@ -1,16 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Shared.Alert; +using JetBrains.Annotations; using Robust.Client.GameObjects; +using Robust.Client.Player; using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; -namespace Content.Client.Alerts +namespace Content.Client.Alerts; + +[UsedImplicitly] +internal class ClientAlertsSystem : AlertsSystem { - internal class ClientAlertsSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); + public AlertOrderPrototype? AlertOrder { get; set; } - SubscribeLocalEvent((_, component, _) => component.PlayerAttached()); - SubscribeLocalEvent((_, component, _) => component.PlayerDetached()); + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public event EventHandler? ClearAlerts; + public event EventHandler>? SyncAlerts; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent((_, component, _) => PlayerAttached(component)); + SubscribeLocalEvent((_, _, _) => PlayerDetached()); + + SubscribeLocalEvent(ClientAlertsHandleState); + } + + protected override void LoadPrototypes() + { + base.LoadPrototypes(); + + AlertOrder = _prototypeManager.EnumeratePrototypes().FirstOrDefault(); + if (AlertOrder == null) + Logger.ErrorS("alert", "no alertOrder prototype found, alerts will be in random order"); + } + + public IReadOnlyDictionary? ActiveAlerts + { + get + { + var ent = _playerManager.LocalPlayer?.ControlledEntity; + return ent is not null + ? GetActiveAlerts(ent.Value) + : null; } } + + protected override void AfterShowAlert(AlertsComponent alertsComponent) + { + if (!CurControlled(alertsComponent.Owner, _playerManager)) + return; + + SyncAlerts?.Invoke(this, alertsComponent.Alerts); + } + + protected override void AfterClearAlert(AlertsComponent alertsComponent) + { + if (!CurControlled(alertsComponent.Owner, _playerManager)) + return; + + SyncAlerts?.Invoke(this, alertsComponent.Alerts); + } + + private void ClientAlertsHandleState(EntityUid uid, AlertsComponent component, ref ComponentHandleState args) + { + var componentAlerts = (args.Current as AlertsComponentState)?.Alerts; + if (componentAlerts == null) return; + + //TODO: Do we really want to send alerts for non-attached entity? + component.Alerts = componentAlerts; + if (!CurControlled(component.Owner, _playerManager)) return; + + SyncAlerts?.Invoke(this, componentAlerts); + } + + private void PlayerAttached(AlertsComponent clientAlertsComponent) + { + if (!CurControlled(clientAlertsComponent.Owner, _playerManager)) return; + SyncAlerts?.Invoke(this, clientAlertsComponent.Alerts); + } + + protected override void HandleComponentShutdown(EntityUid uid) + { + base.HandleComponentShutdown(uid); + + PlayerDetached(); + } + + private void PlayerDetached() + { + ClearAlerts?.Invoke(this, EventArgs.Empty); + } + + public void AlertClicked(AlertType alertType) + { + RaiseNetworkEvent(new ClickAlertEvent(alertType)); + } + + /// + /// Allows calculating if we need to act due to this component being controlled by the current mob + /// + private static bool CurControlled(EntityUid entity, IPlayerManager playerManager) + { + return playerManager.LocalPlayer != null && playerManager.LocalPlayer.ControlledEntity == entity; + } } diff --git a/Content.Client/Alerts/UI/AlertsUI.xaml.cs b/Content.Client/Alerts/UI/AlertsUI.xaml.cs index 0bbbe9d08c..42d31cd463 100644 --- a/Content.Client/Alerts/UI/AlertsUI.xaml.cs +++ b/Content.Client/Alerts/UI/AlertsUI.xaml.cs @@ -1,79 +1,344 @@ -using Content.Client.Chat.Managers; +using System; +using System.Collections.Generic; +using Content.Client.Chat.Managers; using Content.Client.Chat.UI; +using Content.Shared.Alert; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; +using Robust.Shared.GameObjects; +using Robust.Shared.Input; using Robust.Shared.IoC; +using Robust.Shared.Log; -namespace Content.Client.Alerts.UI +namespace Content.Client.Alerts.UI; + +public class AlertsFramePresenter : IDisposable { - /// - /// The status effects display on the right side of the screen. - /// - [GenerateTypedNameReferences] - public sealed partial class AlertsUI : Control + [Dependency] private readonly IEntitySystemManager _systemManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + + private IAlertsFrameView _alertsFrame; + private ClientAlertsSystem? _alertsSystem; + + public AlertsFramePresenter() { - [Dependency] private readonly IChatManager _chatManager = default!; + // This is a lot easier than a factory + IoCManager.InjectDependencies(this); - public const float ChatSeparation = 38f; + _alertsFrame = new AlertsUI(_chatManager); + _userInterfaceManager.StateRoot.AddChild((AlertsUI) _alertsFrame); - public AlertsUI() + // This is required so that if we load after the system is initialized, we can bind to it immediately + if (_systemManager.TryGetEntitySystem(out var alertsSystem)) + SystemBindingChanged(alertsSystem); + + _systemManager.SystemLoaded += OnSystemLoaded; + _systemManager.SystemUnloaded += OnSystemUnloaded; + + _alertsFrame.AlertPressed += OnAlertPressed; + + // initially populate the frame if system is available + var alerts = alertsSystem?.ActiveAlerts; + if (alerts != null) { - IoCManager.InjectDependencies(this); - RobustXamlLoader.Load(this); - - LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin); - LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End); - LayoutContainer.SetAnchorTop(this, 0f); - LayoutContainer.SetAnchorRight(this, 1f); - LayoutContainer.SetAnchorBottom(this, 1f); - LayoutContainer.SetMarginBottom(this, -180); - LayoutContainer.SetMarginTop(this, 250); - LayoutContainer.SetMarginRight(this, -10); + SystemOnSyncAlerts(alertsSystem, alerts); } + } - protected override void EnteredTree() + /// + public void Dispose() + { + _userInterfaceManager.StateRoot.RemoveChild((AlertsUI) _alertsFrame); + _alertsFrame.Dispose(); + _alertsFrame = null!; + + SystemBindingChanged(null); + _systemManager.SystemLoaded -= OnSystemLoaded; + _systemManager.SystemUnloaded -= OnSystemUnloaded; + } + + private void OnAlertPressed(object? sender, AlertType e) + { + _alertsSystem?.AlertClicked(e); + } + + private void SystemOnClearAlerts(object? sender, EventArgs e) + { + _alertsFrame.ClearAllControls(); + } + + private void SystemOnSyncAlerts(object? sender, IReadOnlyDictionary e) + { + if (sender is ClientAlertsSystem system) + _alertsFrame.SyncControls(system, system.AlertOrder, e); + } + + //TODO: This system binding boilerplate seems to be duplicated between every presenter + // prob want to pull it out into a generic object with callbacks for Onbind/OnUnbind + #region System Binding + + private void OnSystemLoaded(object? sender, SystemChangedArgs args) + { + if (args.System is ClientAlertsSystem system) SystemBindingChanged(system); + } + + private void OnSystemUnloaded(object? sender, SystemChangedArgs args) + { + if (args.System is ClientAlertsSystem) SystemBindingChanged(null); + } + + private void SystemBindingChanged(ClientAlertsSystem? newSystem) + { + if (newSystem is null) { - base.EnteredTree(); - _chatManager.OnChatBoxResized += OnChatResized; - OnChatResized(new ChatResizedEventArgs(HudChatBox.InitialChatBottom)); + if (_alertsSystem is null) + return; + + UnbindFromSystem(); } - - protected override void ExitedTree() + else { - base.ExitedTree(); - _chatManager.OnChatBoxResized -= OnChatResized; - } - - private void OnChatResized(ChatResizedEventArgs chatResizedEventArgs) - { - // resize us to fit just below the chatbox - if (_chatManager.CurrentChatBox != null) + if (_alertsSystem is null) { - LayoutContainer.SetMarginTop(this, chatResizedEventArgs.NewBottom + ChatSeparation); + BindToSystem(newSystem); + return; + } + + UnbindFromSystem(); + BindToSystem(newSystem); + } + } + + private void BindToSystem(ClientAlertsSystem system) + { + _alertsSystem = system; + system.SyncAlerts += SystemOnSyncAlerts; + system.ClearAlerts += SystemOnClearAlerts; + } + + private void UnbindFromSystem() + { + var system = _alertsSystem; + + if (system is null) + throw new InvalidOperationException(); + + system.SyncAlerts -= SystemOnSyncAlerts; + system.ClearAlerts -= SystemOnClearAlerts; + } + + #endregion +} + +/// +/// This is the frame of vertical set of alerts that show up on the HUD. +/// +public interface IAlertsFrameView : IDisposable +{ + event EventHandler? AlertPressed; + + void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype, + IReadOnlyDictionary alertStates); + void ClearAllControls(); +} + +/// +/// The status effects display on the right side of the screen. +/// +[GenerateTypedNameReferences] +public sealed partial class AlertsUI : Control, IAlertsFrameView +{ + // also known as Control.Children? + private readonly Dictionary _alertControls = new(); + + public AlertsUI(IChatManager chatManager) + { + _chatManager = chatManager; + RobustXamlLoader.Load(this); + + LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin); + LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.End); + LayoutContainer.SetAnchorTop(this, 0f); + LayoutContainer.SetAnchorRight(this, 1f); + LayoutContainer.SetAnchorBottom(this, 1f); + LayoutContainer.SetMarginBottom(this, -180); + LayoutContainer.SetMarginTop(this, 250); + LayoutContainer.SetMarginRight(this, -10); + } + + public void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype, + IReadOnlyDictionary alertStates) + { + // remove any controls with keys no longer present + if (SyncRemoveControls(alertStates)) return; + + // now we know that alertControls contains alerts that should still exist but + // may need to updated, + // also there may be some new alerts we need to show. + // further, we need to ensure they are ordered w.r.t their configured order + SyncUpdateControls(alertsSystem, alertOrderPrototype, alertStates); + } + + public void ClearAllControls() + { + foreach (var alertControl in _alertControls.Values) + { + alertControl.OnPressed -= AlertControlPressed; + alertControl.Dispose(); + } + + _alertControls.Clear(); + } + + public event EventHandler? AlertPressed; + + //TODO: This control caring about it's layout relative to other controls in the tree is terrible + // the presenters or gamescreen should be dealing with this + // probably want to tackle this after chatbox gets MVP'd + #region Spaghetti + + public const float ChatSeparation = 38f; + private readonly IChatManager _chatManager; + + protected override void EnteredTree() + { + base.EnteredTree(); + _chatManager.OnChatBoxResized += OnChatResized; + OnChatResized(new ChatResizedEventArgs(HudChatBox.InitialChatBottom)); + } + + protected override void ExitedTree() + { + base.ExitedTree(); + _chatManager.OnChatBoxResized -= OnChatResized; + } + + private void OnChatResized(ChatResizedEventArgs chatResizedEventArgs) + { + // resize us to fit just below the chat box + if (_chatManager.CurrentChatBox != null) + LayoutContainer.SetMarginTop(this, chatResizedEventArgs.NewBottom + ChatSeparation); + else + LayoutContainer.SetMarginTop(this, 250); + } + + #endregion + + // This makes no sense but I'm leaving it in place in case I break anything by removing it. + protected override void Resized() + { + // TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done, + // this is here because there isn't currently a good way to allow the grid to adjust its height based + // on constraints, otherwise we would use anchors to lay it out + base.Resized(); + AlertContainer.MaxGridHeight = Height; + } + + protected override void UIScaleChanged() + { + AlertContainer.MaxGridHeight = Height; + base.UIScaleChanged(); + } + + private bool SyncRemoveControls(IReadOnlyDictionary alertStates) + { + var toRemove = new List(); + foreach (var existingKey in _alertControls.Keys) + { + if (!alertStates.ContainsKey(existingKey)) toRemove.Add(existingKey); + } + + foreach (var alertKeyToRemove in toRemove) + { + _alertControls.Remove(alertKeyToRemove, out var control); + if (control == null) return true; + AlertContainer.Children.Remove(control); + } + + return false; + } + + private void SyncUpdateControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype, + IReadOnlyDictionary alertStates) + { + foreach (var (alertKey, alertState) in alertStates) + { + if (!alertKey.AlertType.HasValue) + { + Logger.WarningS("alert", "found alertkey without alerttype," + + " alert keys should never be stored without an alerttype set: {0}", alertKey); + continue; + } + + var alertType = alertKey.AlertType.Value; + if (!alertsSystem.TryGet(alertType, out var newAlert)) + { + Logger.ErrorS("alert", "Unrecognized alertType {0}", alertType); + continue; + } + + if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) && + existingAlertControl.Alert.AlertType == newAlert.AlertType) + { + // key is the same, simply update the existing control severity / cooldown + existingAlertControl.SetSeverity(alertState.Severity); + existingAlertControl.Cooldown = alertState.Cooldown; } else { - LayoutContainer.SetMarginTop(this, 250); + if (existingAlertControl != null) AlertContainer.Children.Remove(existingAlertControl); + + // this is a new alert + alert key or just a different alert with the same + // key, create the control and add it in the appropriate order + var newAlertControl = CreateAlertControl(newAlert, alertState); + + //TODO: Can the presenter sort the states before giving it to us? + if (alertOrderPrototype != null) + { + var added = false; + foreach (var alertControl in AlertContainer.Children) + { + if (alertOrderPrototype.Compare(newAlert, ((AlertControl) alertControl).Alert) >= 0) + continue; + + var idx = alertControl.GetPositionInParent(); + AlertContainer.Children.Add(newAlertControl); + newAlertControl.SetPositionInParent(idx); + added = true; + break; + } + + if (!added) AlertContainer.Children.Add(newAlertControl); + } + else + AlertContainer.Children.Add(newAlertControl); + + _alertControls[newAlert.AlertKey] = newAlertControl; } } + } - // This makes no sense but I'm leaving it in place in case I break anything by removing it. - - protected override void Resized() + private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState) + { + var alertControl = new AlertControl(alert, alertState.Severity) { - // TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done, - // this is here because there isn't currently a good way to allow the grid to adjust its height based - // on constraints, otherwise we would use anchors to lay it out - base.Resized(); - AlertContainer.MaxGridHeight = Height; - } + Cooldown = alertState.Cooldown + }; + alertControl.OnPressed += AlertControlPressed; + return alertControl; + } - protected override void UIScaleChanged() - { - AlertContainer.MaxGridHeight = Height; - base.UIScaleChanged(); - } + private void AlertControlPressed(BaseButton.ButtonEventArgs args) + { + if (args.Button is not AlertControl control) + return; + + if (args.Event.Function != EngineKeyFunctions.UIClick) + return; + + AlertPressed?.Invoke(this, control.Alert.AlertType); } } diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index fee72f5096..6d27ad6eff 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -190,7 +190,6 @@ namespace Content.Client.Entry IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index 961d9dd588..d0d0e0da9a 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -19,7 +19,6 @@ using Content.Client.Viewport; using Content.Client.Voting; using Content.Shared.Actions; using Content.Shared.Administration; -using Content.Shared.Alert; using Content.Shared.Module; using Robust.Shared.IoC; @@ -41,7 +40,6 @@ namespace Content.Client.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); diff --git a/Content.Client/Viewport/GameScreen.cs b/Content.Client/Viewport/GameScreen.cs index c58a7de94c..ae6737aaf7 100644 --- a/Content.Client/Viewport/GameScreen.cs +++ b/Content.Client/Viewport/GameScreen.cs @@ -1,4 +1,4 @@ -using Content.Client.Administration.Managers; +using Content.Client.Alerts.UI; using Content.Client.Chat; using Content.Client.Chat.Managers; using Content.Client.Chat.UI; @@ -38,6 +38,7 @@ namespace Content.Client.Viewport [ViewVariables] private ChatBox? _gameChat; private ConstructionMenuPresenter? _constructionMenu; + private AlertsFramePresenter? _alertsFramePresenter; private FpsCounter _fpsCounter = default!; @@ -107,6 +108,10 @@ namespace Content.Client.Viewport /// private void SetupPresenters() { + // HUD + _alertsFramePresenter = new AlertsFramePresenter(); + + // Windows _constructionMenu = new ConstructionMenuPresenter(_gameHud); } @@ -115,7 +120,11 @@ namespace Content.Client.Viewport /// private void DisposePresenters() { + // Windows _constructionMenu?.Dispose(); + + // HUD + _alertsFramePresenter?.Dispose(); } internal static void FocusChat(ChatBox chat) diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs index e454fb179b..96c0d67c9d 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs @@ -1,20 +1,17 @@ using System.Linq; using System.Threading.Tasks; -using Content.Client.Alerts; using Content.Client.Alerts.UI; -using Content.Server.Alert; using Content.Shared.Alert; using NUnit.Framework; using Robust.Client.UserInterface; -using Robust.Server.Player; using Robust.Shared.GameObjects; +using Robust.Server.Player; using Robust.Shared.IoC; namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs { [TestFixture] - [TestOf(typeof(ClientAlertsComponent))] - [TestOf(typeof(ServerAlertsComponent))] + [TestOf(typeof(AlertsComponent))] public class AlertsComponentTests : ContentIntegrationTest { [Test] @@ -26,17 +23,18 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs await client.WaitIdleAsync(); var serverPlayerManager = server.ResolveDependency(); + var alertsSystem = server.ResolveDependency().GetEntitySystem(); await server.WaitAssertion(() => { var playerEnt = serverPlayerManager.Sessions.Single().AttachedEntity.GetValueOrDefault(); Assert.That(playerEnt != default); - var alertsComponent = IoCManager.Resolve().GetComponent(playerEnt); + var alertsComponent = IoCManager.Resolve().GetComponent(playerEnt); Assert.NotNull(alertsComponent); // show 2 alerts - alertsComponent.ShowAlert(AlertType.Debug1); - alertsComponent.ShowAlert(AlertType.Debug2); + alertsSystem.ShowAlert(alertsComponent.Owner, AlertType.Debug1, null, null); + alertsSystem.ShowAlert(alertsComponent.Owner, AlertType.Debug2, null, null); }); await server.WaitRunTicks(5); @@ -51,7 +49,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs Assert.NotNull(local); var controlled = local.ControlledEntity; Assert.NotNull(controlled); - var alertsComponent = IoCManager.Resolve().GetComponent(controlled.Value); + var alertsComponent = IoCManager.Resolve().GetComponent(controlled.Value); Assert.NotNull(alertsComponent); // find the alertsui @@ -71,10 +69,10 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs { var playerEnt = serverPlayerManager.Sessions.Single().AttachedEntity.GetValueOrDefault(); Assert.That(playerEnt, Is.Not.EqualTo(default)); - var alertsComponent = IoCManager.Resolve().GetComponent(playerEnt); + var alertsComponent = IoCManager.Resolve().GetComponent(playerEnt); Assert.NotNull(alertsComponent); - alertsComponent.ClearAlert(AlertType.Debug1); + alertsSystem.ClearAlert(alertsComponent.Owner, AlertType.Debug1); }); await server.WaitRunTicks(5); await client.WaitRunTicks(5); @@ -86,7 +84,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs Assert.NotNull(local); var controlled = local.ControlledEntity; Assert.NotNull(controlled); - var alertsComponent = IoCManager.Resolve().GetComponent(controlled.Value); + var alertsComponent = IoCManager.Resolve().GetComponent(controlled.Value); Assert.NotNull(alertsComponent); // find the alertsui diff --git a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs index 862c30d6fe..1e5b8b6335 100644 --- a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs +++ b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Content.Server.Gravity; using Content.Server.Gravity.EntitySystems; using Content.Shared.Alert; @@ -42,9 +42,9 @@ namespace Content.IntegrationTests.Tests.Gravity var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); + var alertsSystem = server.ResolveDependency().GetEntitySystem(); EntityUid human = default; - SharedAlertsComponent alerts = null; await server.WaitAssertion(() => { @@ -52,7 +52,7 @@ namespace Content.IntegrationTests.Tests.Gravity var coordinates = grid.ToCoordinates(); human = entityManager.SpawnEntity("HumanDummy", coordinates); - Assert.True(entityManager.TryGetComponent(human, out alerts)); + Assert.True(entityManager.TryGetComponent(human, out AlertsComponent alerts)); }); // Let WeightlessSystem and GravitySystem tick @@ -61,7 +61,7 @@ namespace Content.IntegrationTests.Tests.Gravity await server.WaitAssertion(() => { // No gravity without a gravity generator - Assert.True(alerts.IsShowingAlert(AlertType.Weightless)); + Assert.True(alertsSystem.IsShowingAlert(human, AlertType.Weightless)); entityManager.SpawnEntity("GravityGeneratorDummy", entityManager.GetComponent(human).Coordinates); }); @@ -71,7 +71,7 @@ namespace Content.IntegrationTests.Tests.Gravity await server.WaitAssertion(() => { - Assert.False(alerts.IsShowingAlert(AlertType.Weightless)); + Assert.False(alertsSystem.IsShowingAlert(human, AlertType.Weightless)); // TODO: Re-add gravity generator breaking when Vera is done with construction stuff. /* diff --git a/Content.Server/Alert/Click/RemoveCuffs.cs b/Content.Server/Alert/Click/RemoveCuffs.cs index af55ca2617..ffc7f2ff7f 100644 --- a/Content.Server/Alert/Click/RemoveCuffs.cs +++ b/Content.Server/Alert/Click/RemoveCuffs.cs @@ -14,11 +14,11 @@ namespace Content.Server.Alert.Click [DataDefinition] public class RemoveCuffs : IAlertClick { - public void AlertClicked(ClickAlertEventArgs args) + public void AlertClicked(EntityUid player) { - if (IoCManager.Resolve().TryGetComponent(args.Player, out CuffableComponent? cuffableComponent)) + if (IoCManager.Resolve().TryGetComponent(player, out CuffableComponent? cuffableComponent)) { - cuffableComponent.TryUncuff(args.Player); + cuffableComponent.TryUncuff(player); } } } diff --git a/Content.Server/Alert/Click/ResistFire.cs b/Content.Server/Alert/Click/ResistFire.cs index 8a980b1037..1b5660623a 100644 --- a/Content.Server/Alert/Click/ResistFire.cs +++ b/Content.Server/Alert/Click/ResistFire.cs @@ -1,4 +1,4 @@ -using Content.Server.Atmos.Components; +using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Shared.Alert; using JetBrains.Annotations; @@ -15,11 +15,11 @@ namespace Content.Server.Alert.Click [DataDefinition] public class ResistFire : IAlertClick { - public void AlertClicked(ClickAlertEventArgs args) + public void AlertClicked(EntityUid player) { - if (IoCManager.Resolve().TryGetComponent(args.Player, out FlammableComponent? flammable)) + if (IoCManager.Resolve().TryGetComponent(player, out FlammableComponent? flammable)) { - EntitySystem.Get().Resist(args.Player, flammable); + EntitySystem.Get().Resist(player, flammable); } } } diff --git a/Content.Server/Alert/Click/StopBeingPulled.cs b/Content.Server/Alert/Click/StopBeingPulled.cs index 2d76c98347..4046b324bd 100644 --- a/Content.Server/Alert/Click/StopBeingPulled.cs +++ b/Content.Server/Alert/Click/StopBeingPulled.cs @@ -16,12 +16,12 @@ namespace Content.Server.Alert.Click [DataDefinition] public class StopBeingPulled : IAlertClick { - public void AlertClicked(ClickAlertEventArgs args) + public void AlertClicked(EntityUid player) { - if (!EntitySystem.Get().CanInteract(args.Player)) + if (!EntitySystem.Get().CanInteract(player)) return; - if (IoCManager.Resolve().TryGetComponent(args.Player, out var playerPullable)) + if (IoCManager.Resolve().TryGetComponent(player, out var playerPullable)) { EntitySystem.Get().TryStopPull(playerPullable); } diff --git a/Content.Server/Alert/Click/StopPiloting.cs b/Content.Server/Alert/Click/StopPiloting.cs index 9b1ccabed7..bb624a2951 100644 --- a/Content.Server/Alert/Click/StopPiloting.cs +++ b/Content.Server/Alert/Click/StopPiloting.cs @@ -1,4 +1,4 @@ -using Content.Server.Shuttles; +using Content.Server.Shuttles; using Content.Server.Shuttles.EntitySystems; using Content.Shared.Alert; using Content.Shared.Shuttles; @@ -17,9 +17,9 @@ namespace Content.Server.Alert.Click [DataDefinition] public class StopPiloting : IAlertClick { - public void AlertClicked(ClickAlertEventArgs args) + public void AlertClicked(EntityUid player) { - if (IoCManager.Resolve().TryGetComponent(args.Player, out PilotComponent? pilotComponent) && + if (IoCManager.Resolve().TryGetComponent(player, out PilotComponent? pilotComponent) && pilotComponent.Console != null) { EntitySystem.Get().RemovePilot(pilotComponent); diff --git a/Content.Server/Alert/Click/StopPulling.cs b/Content.Server/Alert/Click/StopPulling.cs index 30d3d11cce..f0381097a6 100644 --- a/Content.Server/Alert/Click/StopPulling.cs +++ b/Content.Server/Alert/Click/StopPulling.cs @@ -15,10 +15,10 @@ namespace Content.Server.Alert.Click [DataDefinition] public class StopPulling : IAlertClick { - public void AlertClicked(ClickAlertEventArgs args) + public void AlertClicked(EntityUid player) { var ps = EntitySystem.Get(); - var playerTarget = ps.GetPulled(args.Player); + var playerTarget = ps.GetPulled(player); if (playerTarget != default && IoCManager.Resolve().TryGetComponent(playerTarget, out SharedPullableComponent playerPullable)) { ps.TryStopPull(playerPullable); diff --git a/Content.Server/Alert/Click/Unbuckle.cs b/Content.Server/Alert/Click/Unbuckle.cs index 9515320efe..cc848aa02b 100644 --- a/Content.Server/Alert/Click/Unbuckle.cs +++ b/Content.Server/Alert/Click/Unbuckle.cs @@ -1,4 +1,4 @@ -using Content.Server.Buckle.Components; +using Content.Server.Buckle.Components; using Content.Shared.Alert; using JetBrains.Annotations; using Robust.Shared.GameObjects; @@ -14,11 +14,11 @@ namespace Content.Server.Alert.Click [DataDefinition] public class Unbuckle : IAlertClick { - public void AlertClicked(ClickAlertEventArgs args) + public void AlertClicked(EntityUid player) { - if (IoCManager.Resolve().TryGetComponent(args.Player, out BuckleComponent? buckle)) + if (IoCManager.Resolve().TryGetComponent(player, out BuckleComponent? buckle)) { - buckle.TryUnbuckle(args.Player); + buckle.TryUnbuckle(player); } } } diff --git a/Content.Server/Alert/Commands/ClearAlert.cs b/Content.Server/Alert/Commands/ClearAlert.cs index 3348d3bf3b..87a031405d 100644 --- a/Content.Server/Alert/Commands/ClearAlert.cs +++ b/Content.Server/Alert/Commands/ClearAlert.cs @@ -34,21 +34,21 @@ namespace Content.Server.Alert.Commands if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return; } - if (!IoCManager.Resolve().TryGetComponent(attachedEntity, out ServerAlertsComponent? alertsComponent)) + if (!IoCManager.Resolve().TryGetComponent(attachedEntity, out AlertsComponent? alertsComponent)) { shell.WriteLine("user has no alerts component"); return; } var alertType = args[0]; - var alertMgr = IoCManager.Resolve(); - if (!alertMgr.TryGet(Enum.Parse(alertType), out var alert)) + var alertsSystem = EntitySystem.Get(); + if (!alertsSystem.TryGet(Enum.Parse(alertType), out var alert)) { shell.WriteLine("unrecognized alertType " + alertType); return; } - alertsComponent.ClearAlert(alert.AlertType); + alertsSystem.ClearAlert(attachedEntity, alert.AlertType); } } } diff --git a/Content.Server/Alert/Commands/ShowAlert.cs b/Content.Server/Alert/Commands/ShowAlert.cs index 04769bc3d2..c676eeb94c 100644 --- a/Content.Server/Alert/Commands/ShowAlert.cs +++ b/Content.Server/Alert/Commands/ShowAlert.cs @@ -34,7 +34,7 @@ namespace Content.Server.Alert.Commands if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return; } - if (!IoCManager.Resolve().TryGetComponent(attachedEntity, out ServerAlertsComponent? alertsComponent)) + if (!IoCManager.Resolve().TryGetComponent(attachedEntity, out AlertsComponent? alertsComponent)) { shell.WriteLine("user has no alerts component"); return; @@ -42,8 +42,8 @@ namespace Content.Server.Alert.Commands var alertType = args[0]; var severity = args[1]; - var alertMgr = IoCManager.Resolve(); - if (!alertMgr.TryGet(Enum.Parse(alertType), out var alert)) + var alertsSystem = EntitySystem.Get(); + if (!alertsSystem.TryGet(Enum.Parse(alertType), out var alert)) { shell.WriteLine("unrecognized alertType " + alertType); return; @@ -53,7 +53,9 @@ namespace Content.Server.Alert.Commands shell.WriteLine("invalid severity " + sevint); return; } - alertsComponent.ShowAlert(alert.AlertType, sevint == -1 ? null : sevint); + + short? severity1 = sevint == -1 ? null : sevint; + alertsSystem.ShowAlert(attachedEntity, alert.AlertType, severity1, null); } } } diff --git a/Content.Server/Alert/ServerAlertsComponent.cs b/Content.Server/Alert/ServerAlertsComponent.cs deleted file mode 100644 index 954838a1ba..0000000000 --- a/Content.Server/Alert/ServerAlertsComponent.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using Content.Server.Gravity.EntitySystems; -using Content.Shared.Alert; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Network; -using Robust.Shared.Players; - -namespace Content.Server.Alert -{ - [RegisterComponent] - [ComponentReference(typeof(SharedAlertsComponent))] - public sealed class ServerAlertsComponent : SharedAlertsComponent - { - - protected override void Startup() - { - base.Startup(); - - if (EntitySystem.TryGet(out var weightlessSystem)) - { - weightlessSystem.AddAlert(this); - } - else - { - Logger.WarningS("alert", "weightlesssystem not found"); - } - } - - protected override void OnRemove() - { - if (EntitySystem.TryGet(out var weightlessSystem)) - { - weightlessSystem.RemoveAlert(this); - } - else - { - Logger.WarningS("alert", $"{nameof(WeightlessSystem)} not found"); - } - - base.OnRemove(); - } - - [Obsolete("Component Messages are deprecated, use Entity Events instead.")] - public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) - { - base.HandleNetworkMessage(message, netChannel, session); - - if (session == null) - { - throw new ArgumentNullException(nameof(session)); - } - - switch (message) - { - case ClickAlertMessage msg: - { - var player = session.AttachedEntity.GetValueOrDefault(); - - if (player != Owner) - { - break; - } - - if (!IsShowingAlert(msg.Type)) - { - Logger.DebugS("alert", "user {0} attempted to" + - " click alert {1} which is not currently showing for them", - IoCManager.Resolve().GetComponent(player).EntityName, msg.Type); - break; - } - - if (!AlertManager.TryGet(msg.Type, out var alert)) - { - Logger.WarningS("alert", "unrecognized encoded alert {0}", msg.Type); - break; - } - - alert.OnClick?.AlertClicked(new ClickAlertEventArgs(player, alert)); - break; - } - } - } - } -} diff --git a/Content.Server/Alert/ServerAlertsSystem.cs b/Content.Server/Alert/ServerAlertsSystem.cs new file mode 100644 index 0000000000..0398dd4b17 --- /dev/null +++ b/Content.Server/Alert/ServerAlertsSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Alert; + +namespace Content.Server.Alert; + +// The only reason this exists is because the DI system requires the shared AlertsSystem +// to be abstract. +internal class ServerAlertsSystem : AlertsSystem { } diff --git a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs index ebd18caf5c..a5195f8c06 100644 --- a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs +++ b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs @@ -1,6 +1,5 @@ using System; using Content.Server.Administration.Logs; -using Content.Server.Alert; using Content.Server.Atmos.Components; using Content.Shared.Alert; using Content.Shared.Atmos; @@ -16,11 +15,11 @@ namespace Content.Server.Atmos.EntitySystems { [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly AdminLogSystem _logSystem = default!; private const float UpdateTimer = 1f; - - private float _timer = 0f; + private float _timer; public override void Initialize() { @@ -72,7 +71,7 @@ namespace Content.Server.Atmos.EntitySystems _timer -= UpdateTimer; - foreach (var (barotrauma, damageable, transform) in EntityManager.EntityQuery(false)) + foreach (var (barotrauma, damageable, transform) in EntityManager.EntityQuery()) { var totalDamage = FixedPoint2.Zero; foreach (var (barotraumaDamageType, _) in barotrauma.Damage.DamageDict) @@ -84,28 +83,24 @@ namespace Content.Server.Atmos.EntitySystems if (totalDamage >= barotrauma.MaxDamage) continue; - var uid = barotrauma.Owner; - - var status = EntityManager.GetComponentOrNull(barotrauma.Owner); - var pressure = 1f; if (_atmosphereSystem.GetTileMixture(transform.Coordinates) is { } mixture) { - pressure = MathF.Max(mixture.Pressure, 1f);; + pressure = MathF.Max(mixture.Pressure, 1f); } switch (pressure) { // Low pressure. case <= Atmospherics.WarningLowPressure: - pressure = GetFeltLowPressure(uid, pressure); + pressure = GetFeltLowPressure(barotrauma.Owner, pressure); if (pressure > Atmospherics.WarningLowPressure) goto default; // Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear. - _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * Atmospherics.LowPressureDamage, true, false); + _damageableSystem.TryChangeDamage(barotrauma.Owner, barotrauma.Damage * Atmospherics.LowPressureDamage, true, false); if (!barotrauma.TakingDamage) { @@ -113,20 +108,18 @@ namespace Content.Server.Atmos.EntitySystems _logSystem.Add(LogType.Barotrauma, $"{ToPrettyString(barotrauma.Owner):entity} started taking low pressure damage"); } - if (status == null) break; - if (pressure <= Atmospherics.HazardLowPressure) { - status.ShowAlert(AlertType.LowPressure, 2); + _alertsSystem.ShowAlert(barotrauma.Owner, AlertType.LowPressure, 2); break; } - status.ShowAlert(AlertType.LowPressure, 1); + _alertsSystem.ShowAlert(barotrauma.Owner, AlertType.LowPressure, 1); break; // High pressure. case >= Atmospherics.WarningHighPressure: - pressure = GetFeltHighPressure(uid, pressure); + pressure = GetFeltHighPressure(barotrauma.Owner, pressure); if(pressure < Atmospherics.WarningHighPressure) goto default; @@ -134,7 +127,7 @@ namespace Content.Server.Atmos.EntitySystems var damageScale = MathF.Min((pressure / Atmospherics.HazardHighPressure) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage); // Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear. - _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false); + _damageableSystem.TryChangeDamage(barotrauma.Owner, barotrauma.Damage * damageScale, true, false); if (!barotrauma.TakingDamage) { @@ -142,15 +135,13 @@ namespace Content.Server.Atmos.EntitySystems _logSystem.Add(LogType.Barotrauma, $"{ToPrettyString(barotrauma.Owner):entity} started taking high pressure damage"); } - if (status == null) break; - if (pressure >= Atmospherics.HazardHighPressure) { - status.ShowAlert(AlertType.HighPressure, 2); + _alertsSystem.ShowAlert(barotrauma.Owner, AlertType.HighPressure, 2); break; } - status.ShowAlert(AlertType.HighPressure, 1); + _alertsSystem.ShowAlert(barotrauma.Owner, AlertType.HighPressure, 1); break; // Normal pressure. @@ -160,7 +151,7 @@ namespace Content.Server.Atmos.EntitySystems barotrauma.TakingDamage = false; _logSystem.Add(LogType.Barotrauma, $"{ToPrettyString(barotrauma.Owner):entity} stopped taking pressure damage"); } - status?.ClearAlertCategory(AlertCategory.Pressure); + _alertsSystem.ClearAlertCategory(barotrauma.Owner, AlertCategory.Pressure); break; } } diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index 111bedc2e1..6d546d3eb5 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Content.Server.Administration.Logs; -using Content.Server.Alert; using Content.Server.Atmos.Components; using Content.Server.Stunnable; using Content.Server.Temperature.Systems; @@ -28,6 +27,7 @@ namespace Content.Server.Atmos.EntitySystems [Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly TemperatureSystem _temperatureSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly AdminLogSystem _logSystem = default!; private const float MinimumFireStacks = -10f; @@ -167,10 +167,9 @@ namespace Content.Server.Atmos.EntitySystems } public void Resist(EntityUid uid, - FlammableComponent? flammable = null, - ServerAlertsComponent? alerts = null) + FlammableComponent? flammable = null) { - if (!Resolve(uid, ref flammable, ref alerts)) + if (!Resolve(uid, ref flammable)) return; if (!flammable.OnFire || !_actionBlockerSystem.CanInteract(flammable.Owner) || flammable.Resisting) @@ -179,7 +178,7 @@ namespace Content.Server.Atmos.EntitySystems flammable.Resisting = true; flammable.Owner.PopupMessage(Loc.GetString("flammable-component-resist-message")); - _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true, alerts: alerts); + _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(2f), true); // TODO FLAMMABLE: Make this not use TimerComponent... flammable.Owner.SpawnTimer(2000, () => @@ -224,15 +223,13 @@ namespace Content.Server.Atmos.EntitySystems flammable.FireStacks = MathF.Min(0, flammable.FireStacks + 1); } - EntityManager.TryGetComponent(flammable.Owner, out ServerAlertsComponent? status); - if (!flammable.OnFire) { - status?.ClearAlert(AlertType.Fire); + _alertsSystem.ClearAlert(uid, AlertType.Fire); continue; } - status?.ShowAlert(AlertType.Fire); + _alertsSystem.ShowAlert(uid, AlertType.Fire, null, null); if (flammable.FireStacks > 0) { diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 8bb39d3197..323a019a05 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using Content.Server.Administration.Logs; -using Content.Server.Alert; using Content.Server.Atmos; using Content.Server.Body.Components; using Content.Shared.Alert; @@ -24,6 +23,7 @@ namespace Content.Server.Body.Systems [Dependency] private readonly AdminLogSystem _logSys = default!; [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly LungSystem _lungSystem = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; public override void Update(float frameTime) { @@ -199,10 +199,7 @@ namespace Content.Server.Body.Systems respirator.Suffocating = true; - if (EntityManager.TryGetComponent(uid, out ServerAlertsComponent? alertsComponent)) - { - alertsComponent.ShowAlert(AlertType.LowOxygen); - } + _alertsSystem.ShowAlert(uid, AlertType.LowOxygen); _damageableSys.TryChangeDamage(uid, respirator.Damage, true, false); } @@ -214,10 +211,7 @@ namespace Content.Server.Body.Systems respirator.Suffocating = false; - if (EntityManager.TryGetComponent(uid, out ServerAlertsComponent? alertsComponent)) - { - alertsComponent.ClearAlert(AlertType.LowOxygen); - } + _alertsSystem.ClearAlert(uid, AlertType.LowOxygen); _damageableSys.TryChangeDamage(uid, respirator.DamageRecovery, true); } diff --git a/Content.Server/Buckle/Components/BuckleComponent.cs b/Content.Server/Buckle/Components/BuckleComponent.cs index 9da4b1eea4..d0542dfe14 100644 --- a/Content.Server/Buckle/Components/BuckleComponent.cs +++ b/Content.Server/Buckle/Components/BuckleComponent.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using Content.Server.Alert; using Content.Server.Hands.Components; using Content.Server.Pulling; using Content.Shared.ActionBlocker; @@ -36,7 +35,6 @@ namespace Content.Server.Buckle.Components [Dependency] private readonly IGameTiming _gameTiming = default!; [ComponentDependency] public readonly AppearanceComponent? Appearance = null; - [ComponentDependency] private readonly ServerAlertsComponent? _serverAlerts = null; [ComponentDependency] private readonly MobStateComponent? _mobState = null; [DataField("size")] @@ -94,18 +92,14 @@ namespace Content.Server.Buckle.Components /// private void UpdateBuckleStatus() { - if (_serverAlerts == null) - { - return; - } - if (Buckled) { - _serverAlerts.ShowAlert(BuckledTo?.BuckledAlertType ?? AlertType.Buckled); + AlertType alertType = BuckledTo?.BuckledAlertType ?? AlertType.Buckled; + EntitySystem.Get().ShowAlert(Owner, alertType); } else { - _serverAlerts.ClearAlertCategory(AlertCategory.Buckled); + EntitySystem.Get().ClearAlertCategory(Owner, AlertCategory.Buckled); } } diff --git a/Content.Server/Clothing/MagbootsSystem.cs b/Content.Server/Clothing/MagbootsSystem.cs index 8b4ad12210..9486e18e33 100644 --- a/Content.Server/Clothing/MagbootsSystem.cs +++ b/Content.Server/Clothing/MagbootsSystem.cs @@ -7,12 +7,15 @@ using Content.Shared.Movement.EntitySystems; using Content.Shared.Slippery; using Content.Shared.Verbs; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Localization; namespace Content.Server.Clothing { public sealed class MagbootsSystem : EntitySystem { + [Dependency] private readonly AlertsSystem _alertsSystem = default!; + public override void Initialize() { base.Initialize(); @@ -35,16 +38,13 @@ namespace Content.Server.Clothing movedByPressure.Enabled = state; } - if (TryComp(parent, out ServerAlertsComponent? alerts)) + if (state) { - if (state) - { - alerts.ShowAlert(AlertType.Magboots); - } - else - { - alerts.ClearAlert(AlertType.Magboots); - } + _alertsSystem.ShowAlert(parent, AlertType.Magboots); + } + else + { + _alertsSystem.ClearAlert(parent, AlertType.Magboots); } } diff --git a/Content.Server/Cuffs/Components/CuffableComponent.cs b/Content.Server/Cuffs/Components/CuffableComponent.cs index 024c68c8ef..740985ae73 100644 --- a/Content.Server/Cuffs/Components/CuffableComponent.cs +++ b/Content.Server/Cuffs/Components/CuffableComponent.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Content.Server.Alert; using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Shared.Alert; @@ -158,16 +157,13 @@ namespace Content.Server.Cuffs.Components /// private void UpdateAlert() { - if (_entMan.TryGetComponent(Owner, out ServerAlertsComponent? status)) + if (CanStillInteract) { - if (CanStillInteract) - { - status.ClearAlert(AlertType.Handcuffed); - } - else - { - status.ShowAlert(AlertType.Handcuffed); - } + EntitySystem.Get().ClearAlert(Owner, AlertType.Handcuffed); + } + else + { + EntitySystem.Get().ShowAlert(Owner, AlertType.Handcuffed); } } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index ae1d6d3f26..dc30f97931 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -9,7 +9,6 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Power.NodeGroups; using Content.Server.Window; -using Content.Shared.Alert; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.Database; @@ -109,8 +108,10 @@ namespace Content.Server.Electrocution var actual = _damageableSystem.TryChangeDamage(finished.Electrocuting, damage); if (actual != null) + { _logSystem.Add(LogType.Electrocution, $"{ToPrettyString(finished.Owner):entity} received {actual.Total:damage} powered electrocution damage"); + } } EntityManager.DeleteEntity(uid); @@ -232,10 +233,10 @@ namespace Content.Server.Electrocution Node? TryNode(string? id) { - if (id != null && nodeContainer.TryGetNode(id, out var node) - && node.NodeGroup is IBasePowerNet { NetworkNode: { LastAvailableSupplySum: >0 } }) + if (id != null && nodeContainer.TryGetNode(id, out var tryNode) + && tryNode.NodeGroup is IBasePowerNet { NetworkNode: { LastAvailableSupplySum: >0 } }) { - return node; + return tryNode; } return null; @@ -245,11 +246,10 @@ namespace Content.Server.Electrocution /// Whether the entity was stunned by the shock. public bool TryDoElectrocution( EntityUid uid, EntityUid? sourceUid, int shockDamage, TimeSpan time, bool refresh, float siemensCoefficient = 1f, - StatusEffectsComponent? statusEffects = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? statusEffects = null) { if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient) - || !DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects, alerts)) + || !DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects)) return false; RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient)); @@ -266,7 +266,6 @@ namespace Content.Server.Electrocution bool refresh, float siemensCoefficient = 1f, StatusEffectsComponent? statusEffects = null, - SharedAlertsComponent? alerts = null, TransformComponent? sourceTransform = null) { if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient)) @@ -274,9 +273,9 @@ namespace Content.Server.Electrocution // Coefficient needs to be higher than this to do a powered electrocution! if(siemensCoefficient <= 0.5f) - return DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects, alerts); + return DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects); - if (!DoCommonElectrocution(uid, sourceUid, null, time, refresh, siemensCoefficient, statusEffects, alerts)) + if (!DoCommonElectrocution(uid, sourceUid, null, time, refresh, siemensCoefficient, statusEffects)) return false; if (!Resolve(sourceUid, ref sourceTransform)) // This shouldn't really happen, but just in case... @@ -318,8 +317,7 @@ namespace Content.Server.Electrocution private bool DoCommonElectrocution(EntityUid uid, EntityUid? sourceUid, int? shockDamage, TimeSpan time, bool refresh, float siemensCoefficient = 1f, - StatusEffectsComponent? statusEffects = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? statusEffects = null) { if (siemensCoefficient <= 0) return false; @@ -332,21 +330,18 @@ namespace Content.Server.Electrocution return false; } - // Optional component. - Resolve(uid, ref alerts, false); - if (!Resolve(uid, ref statusEffects, false) || !_statusEffectsSystem.CanApplyEffect(uid, StatusEffectKey, statusEffects)) return false; if (!_statusEffectsSystem.TryAddStatusEffect(uid, StatusEffectKey, time, refresh, - statusEffects, alerts)) + statusEffects)) return false; var shouldStun = siemensCoefficient > 0.5f; if (shouldStun) - _stunSystem.TryParalyze(uid, time * ParalyzeTimeMultiplier, refresh, statusEffects, alerts); + _stunSystem.TryParalyze(uid, time * ParalyzeTimeMultiplier, refresh, statusEffects); // TODO: Sparks here. @@ -356,13 +351,15 @@ namespace Content.Server.Electrocution new DamageSpecifier(_prototypeManager.Index(DamageType), dmg)); if (actual != null) + { _logSystem.Add(LogType.Electrocution, $"{ToPrettyString(statusEffects.Owner):entity} received {actual.Total:damage} powered electrocution damage"); + } } - _stutteringSystem.DoStutter(uid, time * StutteringTimeMultiplier, refresh, statusEffects, alerts); + _stutteringSystem.DoStutter(uid, time * StutteringTimeMultiplier, refresh, statusEffects); _jitteringSystem.DoJitter(uid, time * JitterTimeMultiplier, refresh, JitterAmplitude, JitterFrequency, true, - statusEffects, alerts); + statusEffects); _popupSystem.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-player"), uid, Filter.Entities(uid).Unpredicted()); diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 9233ce32c8..656ec0b4f5 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -89,7 +89,6 @@ namespace Content.Server.Entry IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); diff --git a/Content.Server/Gravity/EntitySystems/WeightlessSystem.cs b/Content.Server/Gravity/EntitySystems/WeightlessSystem.cs index 6708340cbf..2fcbe2508f 100644 --- a/Content.Server/Gravity/EntitySystems/WeightlessSystem.cs +++ b/Content.Server/Gravity/EntitySystems/WeightlessSystem.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Content.Server.Alert; +using System.Collections.Generic; using Content.Shared.Alert; using Content.Shared.GameTicking; using Content.Shared.Gravity; @@ -15,8 +14,9 @@ namespace Content.Server.Gravity.EntitySystems public class WeightlessSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; - private readonly Dictionary> _alerts = new(); + private readonly Dictionary> _alerts = new(); public override void Initialize() { @@ -25,6 +25,7 @@ namespace Content.Server.Gravity.EntitySystems SubscribeLocalEvent(Reset); SubscribeLocalEvent(GravityChanged); SubscribeLocalEvent(EntParentChanged); + SubscribeLocalEvent(HandleAlertSyncEvent); } public void Reset(RoundRestartCleanupEvent ev) @@ -32,7 +33,7 @@ namespace Content.Server.Gravity.EntitySystems _alerts.Clear(); } - public void AddAlert(ServerAlertsComponent status) + public void AddAlert(AlertsComponent status) { var gridId = EntityManager.GetComponent(status.Owner).GridID; var alerts = _alerts.GetOrNew(gridId); @@ -43,16 +44,16 @@ namespace Content.Server.Gravity.EntitySystems { if (EntityManager.GetComponent(grid.GridEntityId).Enabled) { - RemoveWeightless(status); + RemoveWeightless(status.Owner); } else { - AddWeightless(status); + AddWeightless(status.Owner); } } } - public void RemoveAlert(ServerAlertsComponent status) + public void RemoveAlert(AlertsComponent status) { var grid = EntityManager.GetComponent(status.Owner).GridID; if (!_alerts.TryGetValue(grid, out var statuses)) @@ -74,31 +75,31 @@ namespace Content.Server.Gravity.EntitySystems { foreach (var status in statuses) { - RemoveWeightless(status); + RemoveWeightless(status.Owner); } } else { foreach (var status in statuses) { - AddWeightless(status); + AddWeightless(status.Owner); } } } - private void AddWeightless(ServerAlertsComponent status) + private void AddWeightless(EntityUid euid) { - status.ShowAlert(AlertType.Weightless); + _alertsSystem.ShowAlert(euid, AlertType.Weightless); } - private void RemoveWeightless(ServerAlertsComponent status) + private void RemoveWeightless(EntityUid euid) { - status.ClearAlert(AlertType.Weightless); + _alertsSystem.ClearAlert(euid, AlertType.Weightless); } private void EntParentChanged(ref EntParentChangedMessage ev) { - if (!EntityManager.TryGetComponent(ev.Entity, out ServerAlertsComponent? status)) + if (!EntityManager.TryGetComponent(ev.Entity, out AlertsComponent? status)) { return; } @@ -119,5 +120,18 @@ namespace Content.Server.Gravity.EntitySystems newStatuses.Add(status); } + + private void HandleAlertSyncEvent(EntityUid uid, AlertsComponent component, AlertSyncEvent args) + { + switch (component.LifeStage) + { + case ComponentLifeStage.Starting: + AddAlert(component); + break; + case ComponentLifeStage.Removing: + RemoveAlert(component); + break; + } + } } } diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index bef312b4bb..3a549ebd26 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -7,10 +7,7 @@ using Content.Server.AI.WorldState; using Content.Server.Chat.Managers; using Content.Server.Connection; using Content.Server.Database; -using Content.Server.DeviceNetwork; using Content.Server.EUI; -using Content.Server.Holiday; -using Content.Server.Holiday.Interfaces; using Content.Server.Info; using Content.Server.Maps; using Content.Server.Module; @@ -20,11 +17,9 @@ using Content.Server.Objectives; using Content.Server.Objectives.Interfaces; using Content.Server.Preferences.Managers; using Content.Server.Sandbox; -using Content.Server.Speech; using Content.Server.Voting.Managers; using Content.Shared.Actions; using Content.Shared.Administration; -using Content.Shared.Alert; using Content.Shared.Kitchen; using Content.Shared.Module; using Robust.Shared.IoC; @@ -43,7 +38,6 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); diff --git a/Content.Server/MobState/States/DeadMobState.cs b/Content.Server/MobState/States/DeadMobState.cs index 81d05e66ad..0a7c42fbf3 100644 --- a/Content.Server/MobState/States/DeadMobState.cs +++ b/Content.Server/MobState/States/DeadMobState.cs @@ -1,4 +1,4 @@ -using Content.Server.Alert; +using System; using Content.Server.Stunnable; using Content.Server.Stunnable.Components; using Content.Shared.Alert; @@ -17,10 +17,7 @@ namespace Content.Server.MobState.States { base.EnterState(uid, entityManager); - if (entityManager.TryGetComponent(uid, out ServerAlertsComponent? status)) - { - status.ShowAlert(AlertType.HumanDead); - } + EntitySystem.Get().ShowAlert(uid, AlertType.HumanDead); if (entityManager.TryGetComponent(uid, out StatusEffectsComponent? stun)) { diff --git a/Content.Server/MobState/States/NormalMobState.cs b/Content.Server/MobState/States/NormalMobState.cs index f86b29b151..a5999f0c72 100644 --- a/Content.Server/MobState/States/NormalMobState.cs +++ b/Content.Server/MobState/States/NormalMobState.cs @@ -1,4 +1,4 @@ -using Content.Server.Alert; +using System; using Content.Shared.Alert; using Content.Shared.Damage; using Content.Shared.FixedPoint; @@ -19,11 +19,6 @@ namespace Content.Server.MobState.States return; } - if (!entityManager.TryGetComponent(entity, out ServerAlertsComponent? alerts)) - { - return; - } - if (!entityManager.TryGetComponent(entity, out MobStateComponent? stateComponent)) { return; @@ -36,7 +31,7 @@ namespace Content.Server.MobState.States modifier = (short) (damageable.TotalDamage / (earliestThreshold / 7f)); } - alerts.ShowAlert(AlertType.HumanHealth, modifier); + EntitySystem.Get().ShowAlert(entity, AlertType.HumanHealth, modifier); } } } diff --git a/Content.Server/Nutrition/Components/HungerComponent.cs b/Content.Server/Nutrition/Components/HungerComponent.cs index f89ba4ad59..ffe32a3732 100644 --- a/Content.Server/Nutrition/Components/HungerComponent.cs +++ b/Content.Server/Nutrition/Components/HungerComponent.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Content.Server.Administration.Logs; -using Content.Server.Alert; using Content.Shared.Administration.Logs; using Content.Shared.Alert; using Content.Shared.Damage; @@ -95,15 +94,13 @@ namespace Content.Server.Nutrition.Components } // Update UI - _entMan.TryGetComponent(Owner, out ServerAlertsComponent? alertsComponent); - if (HungerThresholdAlertTypes.TryGetValue(_currentHungerThreshold, out var alertId)) { - alertsComponent?.ShowAlert(alertId); + EntitySystem.Get().ShowAlert(Owner, alertId); } else { - alertsComponent?.ClearAlertCategory(AlertCategory.Hunger); + EntitySystem.Get().ClearAlertCategory(Owner, AlertCategory.Hunger); } switch (_currentHungerThreshold) diff --git a/Content.Server/Nutrition/Components/ThirstComponent.cs b/Content.Server/Nutrition/Components/ThirstComponent.cs index 502500db74..fd5bb4ab8a 100644 --- a/Content.Server/Nutrition/Components/ThirstComponent.cs +++ b/Content.Server/Nutrition/Components/ThirstComponent.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Content.Server.Administration.Logs; -using Content.Server.Alert; using Content.Shared.Administration.Logs; using Content.Shared.Alert; using Content.Shared.Damage; @@ -94,15 +93,13 @@ namespace Content.Server.Nutrition.Components } // Update UI - _entMan.TryGetComponent(Owner, out ServerAlertsComponent? alertsComponent); - if (ThirstThresholdAlertTypes.TryGetValue(_currentThirstThreshold, out var alertId)) { - alertsComponent?.ShowAlert(alertId); + EntitySystem.Get().ShowAlert(Owner, alertId); } else { - alertsComponent?.ClearAlertCategory(AlertCategory.Thirst); + EntitySystem.Get().ClearAlertCategory(Owner, AlertCategory.Thirst); } switch (_currentThirstThreshold) diff --git a/Content.Server/Shuttles/EntitySystems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/EntitySystems/ShuttleConsoleSystem.cs index 5e879cdbc7..348b9825d7 100644 --- a/Content.Server/Shuttles/EntitySystems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/EntitySystems/ShuttleConsoleSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Alert; +using System; using Content.Server.Power.Components; using Content.Server.Shuttles.Components; using Content.Shared.ActionBlocker; @@ -18,6 +18,7 @@ namespace Content.Server.Shuttles.EntitySystems internal sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem { [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; public override void Initialize() { @@ -141,10 +142,7 @@ namespace Content.Server.Shuttles.EntitySystems component.SubscribedPilots.Add(pilotComponent); - if (EntityManager.TryGetComponent(entity, out ServerAlertsComponent? alertsComponent)) - { - alertsComponent.ShowAlert(AlertType.PilotingShuttle); - } + _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle); entity.PopupMessage(Loc.GetString("shuttle-pilot-start")); pilotComponent.Console = component; @@ -163,10 +161,7 @@ namespace Content.Server.Shuttles.EntitySystems if (!helmsman.SubscribedPilots.Remove(pilotComponent)) return; - if (EntityManager.TryGetComponent(pilotComponent.Owner, out ServerAlertsComponent? alertsComponent)) - { - alertsComponent.ClearAlert(AlertType.PilotingShuttle); - } + _alertsSystem.ClearAlert(pilotComponent.Owner, AlertType.PilotingShuttle); pilotComponent.Owner.PopupMessage(Loc.GetString("shuttle-pilot-end")); diff --git a/Content.Server/Speech/EntitySystems/StutteringSystem.cs b/Content.Server/Speech/EntitySystems/StutteringSystem.cs index a6efe37e98..b860b8cf7f 100644 --- a/Content.Server/Speech/EntitySystems/StutteringSystem.cs +++ b/Content.Server/Speech/EntitySystems/StutteringSystem.cs @@ -1,9 +1,7 @@ using System; using System.Text; using System.Text.RegularExpressions; -using Content.Server.Alert; using Content.Server.Speech.Components; -using Content.Shared.Alert; using Content.Shared.Speech.EntitySystems; using Content.Shared.StatusEffect; using Robust.Shared.GameObjects; @@ -28,12 +26,12 @@ namespace Content.Server.Speech.EntitySystems SubscribeLocalEvent(OnAccent); } - public override void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null, SharedAlertsComponent? alerts = null) + public override void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null) { if (!Resolve(uid, ref status, false)) return; - _statusEffectsSystem.TryAddStatusEffect(uid, StutterKey, time, refresh, status, alerts); + _statusEffectsSystem.TryAddStatusEffect(uid, StutterKey, time, refresh, status); } private void OnAccent(EntityUid uid, StutteringAccentComponent component, AccentGetEvent args) diff --git a/Content.Server/Stunnable/StunOnCollideSystem.cs b/Content.Server/Stunnable/StunOnCollideSystem.cs index e88fae7ac0..9143c9b09b 100644 --- a/Content.Server/Stunnable/StunOnCollideSystem.cs +++ b/Content.Server/Stunnable/StunOnCollideSystem.cs @@ -1,5 +1,4 @@ using System; -using Content.Server.Alert; using Content.Server.Stunnable.Components; using Content.Shared.Standing; using Content.Shared.StatusEffect; @@ -27,20 +26,19 @@ namespace Content.Server.Stunnable if (EntityManager.TryGetComponent(otherUid, out var status)) { - ServerAlertsComponent? alerts = null; StandingStateComponent? standingState = null; AppearanceComponent? appearance = null; // Let the actual methods log errors for these. - Resolve(otherUid, ref alerts, ref standingState, ref appearance, false); + Resolve(otherUid, ref standingState, ref appearance, false); - _stunSystem.TryStun(otherUid, TimeSpan.FromSeconds(component.StunAmount), true, status, alerts); + _stunSystem.TryStun(otherUid, TimeSpan.FromSeconds(component.StunAmount), true, status); _stunSystem.TryKnockdown(otherUid, TimeSpan.FromSeconds(component.KnockdownAmount), true, - status, alerts); + status); _stunSystem.TrySlowdown(otherUid, TimeSpan.FromSeconds(component.SlowdownAmount), true, - component.WalkSpeedMultiplier, component.RunSpeedMultiplier, status, alerts); + component.WalkSpeedMultiplier, component.RunSpeedMultiplier, status); } } } diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.cs b/Content.Server/Temperature/Systems/TemperatureSystem.cs index b15f32c305..eee2476d7e 100644 --- a/Content.Server/Temperature/Systems/TemperatureSystem.cs +++ b/Content.Server/Temperature/Systems/TemperatureSystem.cs @@ -2,15 +2,12 @@ using System; using System.Collections.Generic; using System.Linq; using Content.Server.Administration.Logs; -using Content.Server.Alert; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Temperature.Components; -using Content.Shared.Administration.Logs; using Content.Shared.Alert; using Content.Shared.Damage; using Content.Shared.Database; -using Content.Shared.FixedPoint; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -20,6 +17,7 @@ namespace Content.Server.Temperature.Systems { [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly AdminLogSystem _logSystem = default!; /// @@ -31,13 +29,13 @@ namespace Content.Server.Temperature.Systems public float UpdateInterval = 1.0f; - private float _accumulatedFrametime = 0.0f; + private float _accumulatedFrametime; public override void Initialize() { SubscribeLocalEvent(EnqueueDamage); SubscribeLocalEvent(OnAtmosExposedUpdate); - SubscribeLocalEvent(ServerAlert); + SubscribeLocalEvent(ServerAlert); SubscribeLocalEvent(OnTemperatureChangeAttempt); } @@ -103,43 +101,43 @@ namespace Content.Server.Temperature.Systems ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature ); } - private void ServerAlert(EntityUid uid, ServerAlertsComponent status, OnTemperatureChangeEvent args) + private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureChangeEvent args) { switch (args.CurrentTemperature) { // Cold strong. case <= 260: - status.ShowAlert(AlertType.Cold, 3); + _alertsSystem.ShowAlert(uid, AlertType.Cold, 3); break; // Cold mild. case <= 280 and > 260: - status.ShowAlert(AlertType.Cold, 2); + _alertsSystem.ShowAlert(uid, AlertType.Cold, 2); break; // Cold weak. case <= 292 and > 280: - status.ShowAlert(AlertType.Cold, 1); + _alertsSystem.ShowAlert(uid, AlertType.Cold, 1); break; // Safe. case <= 327 and > 292: - status.ClearAlertCategory(AlertCategory.Temperature); + _alertsSystem.ClearAlertCategory(uid, AlertCategory.Temperature); break; // Heat weak. case <= 335 and > 327: - status.ShowAlert(AlertType.Hot, 1); + _alertsSystem.ShowAlert(uid, AlertType.Hot, 1); break; // Heat mild. case <= 360 and > 335: - status.ShowAlert(AlertType.Hot, 2); + _alertsSystem.ShowAlert(uid, AlertType.Hot, 2); break; // Heat strong. case > 360: - status.ShowAlert(AlertType.Hot, 3); + _alertsSystem.ShowAlert(uid, AlertType.Hot, 3); break; } } @@ -151,7 +149,7 @@ namespace Content.Server.Temperature.Systems private void ChangeDamage(EntityUid uid, TemperatureComponent temperature) { - if (!EntityManager.TryGetComponent(uid, out var damage)) + if (!EntityManager.HasComponent(uid)) return; // See this link for where the scaling func comes from: diff --git a/Content.Shared/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs new file mode 100644 index 0000000000..8c7d65ef76 --- /dev/null +++ b/Content.Shared/Alert/AlertCategory.cs @@ -0,0 +1,16 @@ +namespace Content.Shared.Alert; + +/// +/// Every category of alert. Corresponds to category field in alert prototypes defined in YML +/// +public enum AlertCategory +{ + Pressure, + Temperature, + Breathing, + Buckled, + Health, + Piloting, + Hunger, + Thirst +} \ No newline at end of file diff --git a/Content.Shared/Alert/AlertKey.cs b/Content.Shared/Alert/AlertKey.cs new file mode 100644 index 0000000000..f649bf7b6d --- /dev/null +++ b/Content.Shared/Alert/AlertKey.cs @@ -0,0 +1,62 @@ +using System; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager; + +namespace Content.Shared.Alert; + +/// +/// Key for an alert which is unique (for equality and hashcode purposes) w.r.t category semantics. +/// I.e., entirely defined by the category, if a category was specified, otherwise +/// falls back to the id. +/// +[Serializable, NetSerializable] +public struct AlertKey : ISerializationHooks, IPopulateDefaultValues +{ + public AlertType? AlertType { get; private set; } + public readonly AlertCategory? AlertCategory; + + /// NOTE: if the alert has a category you must pass the category for this to work + /// properly as a key. I.e. if the alert has a category and you pass only the alert type, and you + /// compare this to another AlertKey that has both the category and the same alert type, it will not consider them equal. + public AlertKey(AlertType? alertType, AlertCategory? alertCategory) + { + AlertCategory = alertCategory; + AlertType = alertType; + } + + public bool Equals(AlertKey other) + { + // compare only on alert category if we have one + if (AlertCategory.HasValue) + { + return other.AlertCategory == AlertCategory; + } + + return AlertType == other.AlertType && AlertCategory == other.AlertCategory; + } + + public override bool Equals(object? obj) + { + return obj is AlertKey other && Equals(other); + } + + public override int GetHashCode() + { + // use only alert category if we have one + if (AlertCategory.HasValue) return AlertCategory.GetHashCode(); + return AlertType.GetHashCode(); + } + + public void PopulateDefaultValues() + { + AlertType = Alert.AlertType.Error; + } + + /// alert category, must not be null + /// An alert key for the provided alert category. This must only be used for + /// queries and never storage, as it is lacking an alert type. + public static AlertKey ForCategory(AlertCategory category) + { + return new(null, category); + } +} \ No newline at end of file diff --git a/Content.Shared/Alert/AlertManager.cs b/Content.Shared/Alert/AlertManager.cs deleted file mode 100644 index 4fa11c9482..0000000000 --- a/Content.Shared/Alert/AlertManager.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Alert -{ - /// - /// Provides access to all configured alerts by alert type. - /// - public class AlertManager - { - [Dependency] - private readonly IPrototypeManager _prototypeManager = default!; - - private readonly Dictionary _typeToAlert = new(); - - public void Initialize() - { - foreach (var alert in _prototypeManager.EnumeratePrototypes()) - { - if (!_typeToAlert.TryAdd(alert.AlertType, alert)) - { - Logger.ErrorS("alert", - "Found alert with duplicate alertType {0} - all alerts must have" + - " a unique alerttype, this one will be skipped", alert.AlertType); - } - } - } - - /// - /// Tries to get the alert of the indicated type - /// - /// true if found - public bool TryGet(AlertType alertType, [NotNullWhen(true)] out AlertPrototype? alert) - { - return _typeToAlert.TryGetValue(alertType, out alert); - } - } -} diff --git a/Content.Shared/Alert/AlertPrototype.cs b/Content.Shared/Alert/AlertPrototype.cs index 79eafd637d..4bbb1de323 100644 --- a/Content.Shared/Alert/AlertPrototype.cs +++ b/Content.Shared/Alert/AlertPrototype.cs @@ -3,7 +3,6 @@ using System.Globalization; using Robust.Shared.Log; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; @@ -142,61 +141,4 @@ namespace Content.Shared.Alert } } } - - /// - /// Key for an alert which is unique (for equality and hashcode purposes) w.r.t category semantics. - /// I.e., entirely defined by the category, if a category was specified, otherwise - /// falls back to the id. - /// - [Serializable, NetSerializable] - public struct AlertKey : ISerializationHooks, IPopulateDefaultValues - { - public AlertType? AlertType { get; private set; } - public readonly AlertCategory? AlertCategory; - - /// NOTE: if the alert has a category you must pass the category for this to work - /// properly as a key. I.e. if the alert has a category and you pass only the alert type, and you - /// compare this to another AlertKey that has both the category and the same alert type, it will not consider them equal. - public AlertKey(AlertType? alertType, AlertCategory? alertCategory) - { - AlertCategory = alertCategory; - AlertType = alertType; - } - - public bool Equals(AlertKey other) - { - // compare only on alert category if we have one - if (AlertCategory.HasValue) - { - return other.AlertCategory == AlertCategory; - } - - return AlertType == other.AlertType && AlertCategory == other.AlertCategory; - } - - public override bool Equals(object? obj) - { - return obj is AlertKey other && Equals(other); - } - - public override int GetHashCode() - { - // use only alert category if we have one - if (AlertCategory.HasValue) return AlertCategory.GetHashCode(); - return AlertType.GetHashCode(); - } - - public void PopulateDefaultValues() - { - AlertType = Alert.AlertType.Error; - } - - /// alert category, must not be null - /// An alert key for the provided alert category. This must only be used for - /// queries and never storage, as it is lacking an alert type. - public static AlertKey ForCategory(AlertCategory category) - { - return new(null, category); - } - } } diff --git a/Content.Shared/Alert/AlertState.cs b/Content.Shared/Alert/AlertState.cs new file mode 100644 index 0000000000..21cf322fb6 --- /dev/null +++ b/Content.Shared/Alert/AlertState.cs @@ -0,0 +1,12 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.Alert; + +[Serializable, NetSerializable] +public struct AlertState +{ + public short? Severity; + public (TimeSpan, TimeSpan)? Cooldown; + public AlertType Type; +} \ No newline at end of file diff --git a/Content.Shared/Alert/AlertSyncEvent.cs b/Content.Shared/Alert/AlertSyncEvent.cs new file mode 100644 index 0000000000..2c605c36e9 --- /dev/null +++ b/Content.Shared/Alert/AlertSyncEvent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameObjects; + +namespace Content.Shared.Alert; + +/// +/// Raised when the AlertSystem needs alert sources to recalculate their alert states and set them. +/// +public class AlertSyncEvent : EntityEventArgs +{ + public EntityUid Euid { get; } + + public AlertSyncEvent(EntityUid euid) + { + Euid = euid; + } +} diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index a4962a1d36..3c87650419 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -1,20 +1,5 @@ namespace Content.Shared.Alert { - /// - /// Every category of alert. Corresponds to category field in alert prototypes defined in YML - /// - public enum AlertCategory - { - Pressure, - Temperature, - Breathing, - Buckled, - Health, - Piloting, - Hunger, - Thirst - } - /// /// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML /// NOTE: Using byte for a compact encoding when sending this in messages, can upgrade diff --git a/Content.Shared/Alert/AlertsComponent.cs b/Content.Shared/Alert/AlertsComponent.cs new file mode 100644 index 0000000000..a1d16de665 --- /dev/null +++ b/Content.Shared/Alert/AlertsComponent.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.Alert; + +/// +/// Handles the icons on the right side of the screen. +/// Should only be used for player-controlled entities. +/// +[RegisterComponent] +[NetworkedComponent] +[ComponentProtoName("Alerts")] +public class AlertsComponent : Component +{ + [ViewVariables] public Dictionary Alerts = new(); +} diff --git a/Content.Shared/Alert/AlertsComponentState.cs b/Content.Shared/Alert/AlertsComponentState.cs new file mode 100644 index 0000000000..f61bf3b8c6 --- /dev/null +++ b/Content.Shared/Alert/AlertsComponentState.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.Alert; + +[Serializable, NetSerializable] +public class AlertsComponentState : ComponentState +{ + public Dictionary Alerts; + + public AlertsComponentState(Dictionary alerts) + { + Alerts = alerts; + } +} \ No newline at end of file diff --git a/Content.Shared/Alert/AlertsSystem.cs b/Content.Shared/Alert/AlertsSystem.cs new file mode 100644 index 0000000000..8baea2b664 --- /dev/null +++ b/Content.Shared/Alert/AlertsSystem.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Alert; + +public abstract class AlertsSystem : EntitySystem +{ + [Dependency] + private readonly IPrototypeManager _prototypeManager = default!; + + private readonly Dictionary _typeToAlert = new(); + + public IReadOnlyDictionary? GetActiveAlerts(EntityUid euid) + { + return EntityManager.TryGetComponent(euid, out AlertsComponent comp) + ? comp.Alerts + : null; + } + + public bool IsShowingAlert(EntityUid euid, AlertType alertType) + { + if (!EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent)) + return false; + + if (TryGet(alertType, out var alert)) + { + return alertsComponent.Alerts.ContainsKey(alert.AlertKey); + } + + Logger.DebugS("alert", "unknown alert type {0}", alertType); + return false; + } + + /// true iff an alert of the indicated alert category is currently showing + public bool IsShowingAlertCategory(EntityUid euid, AlertCategory alertCategory) + { + return EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent) + && alertsComponent.Alerts.ContainsKey(AlertKey.ForCategory(alertCategory)); + } + + public bool TryGetAlertState(EntityUid euid, AlertKey key, out AlertState alertState) + { + if (EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent)) + return alertsComponent.Alerts.TryGetValue(key, out alertState); + + alertState = default; + return false; + + } + + /// + /// Shows the alert. If the alert or another alert of the same category is already showing, + /// it will be updated / replaced with the specified values. + /// + /// + /// type of the alert to set + /// severity, if supported by the alert + /// cooldown start and end, if null there will be no cooldown (and it will + /// be erased if there is currently a cooldown for the alert) + public void ShowAlert(EntityUid euid, AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null) + { + if (!EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent)) + return; + + if (TryGet(alertType, out var alert)) + { + // Check whether the alert category we want to show is already being displayed, with the same type, + // severity, and cooldown. + if (alertsComponent.Alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) && + alertStateCallback.Type == alertType && + alertStateCallback.Severity == severity && + alertStateCallback.Cooldown == cooldown) + { + return; + } + + // In the case we're changing the alert type but not the category, we need to remove it first. + alertsComponent.Alerts.Remove(alert.AlertKey); + + alertsComponent.Alerts[alert.AlertKey] = new AlertState + { Cooldown = cooldown, Severity = severity, Type = alertType }; + + AfterShowAlert(alertsComponent); + + alertsComponent.Dirty(); + } + else + { + Logger.ErrorS("alert", "Unable to show alert {0}, please ensure this alertType has" + + " a corresponding YML alert prototype", + alertType); + } + } + + /// + /// Clear the alert with the given category, if one is currently showing. + /// + public void ClearAlertCategory(EntityUid euid, AlertCategory category) + { + if(!EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent)) + return; + + var key = AlertKey.ForCategory(category); + if (!alertsComponent.Alerts.Remove(key)) + { + return; + } + + AfterClearAlert(alertsComponent); + + alertsComponent.Dirty(); + } + + /// + /// Clear the alert of the given type if it is currently showing. + /// + public void ClearAlert(EntityUid euid, AlertType alertType) + { + if (!EntityManager.TryGetComponent(euid, out AlertsComponent alertsComponent)) + return; + + if (TryGet(alertType, out var alert)) + { + if (!alertsComponent.Alerts.Remove(alert.AlertKey)) + { + return; + } + + AfterClearAlert(alertsComponent); + + alertsComponent.Dirty(); + } + else + { + Logger.ErrorS("alert", "unable to clear alert, unknown alertType {0}", alertType); + } + } + + /// + /// Invoked after showing an alert prior to dirtying the component + /// + /// + protected virtual void AfterShowAlert(AlertsComponent alertsComponent) { } + + /// + /// Invoked after clearing an alert prior to dirtying the component + /// + /// + protected virtual void AfterClearAlert(AlertsComponent alertsComponent) { } + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent((uid, _, _) => RaiseLocalEvent(uid, new AlertSyncEvent(uid))); + SubscribeLocalEvent((uid, _, _) => HandleComponentShutdown(uid)); + + SubscribeLocalEvent(ClientAlertsGetState); + SubscribeNetworkEvent(HandleClickAlert); + + LoadPrototypes(); + _prototypeManager.PrototypesReloaded += HandlePrototypesReloaded; + } + + protected virtual void HandleComponentShutdown(EntityUid uid) + { + RaiseLocalEvent(uid, new AlertSyncEvent(uid)); + } + + public override void Shutdown() + { + _prototypeManager.PrototypesReloaded -= HandlePrototypesReloaded; + + base.Shutdown(); + } + + private void HandlePrototypesReloaded(PrototypesReloadedEventArgs obj) + { + LoadPrototypes(); + } + + protected virtual void LoadPrototypes() + { + _typeToAlert.Clear(); + foreach (var alert in _prototypeManager.EnumeratePrototypes()) + { + if (!_typeToAlert.TryAdd(alert.AlertType, alert)) + { + Logger.ErrorS("alert", + "Found alert with duplicate alertType {0} - all alerts must have" + + " a unique alerttype, this one will be skipped", alert.AlertType); + } + } + } + + /// + /// Tries to get the alert of the indicated type + /// + /// true if found + public bool TryGet(AlertType alertType, [NotNullWhen(true)] out AlertPrototype? alert) + { + return _typeToAlert.TryGetValue(alertType, out alert); + } + + private void HandleClickAlert(ClickAlertEvent msg, EntitySessionEventArgs args) + { + var player = args.SenderSession.AttachedEntity; + if (player is null || !EntityManager.TryGetComponent(player, out var alertComp)) return; + + if (!IsShowingAlert(player.Value, msg.Type)) + { + Logger.DebugS("alert", "user {0} attempted to" + + " click alert {1} which is not currently showing for them", + EntityManager.GetComponent(player.Value).EntityName, msg.Type); + return; + } + + if (!TryGet(msg.Type, out var alert)) + { + Logger.WarningS("alert", "unrecognized encoded alert {0}", msg.Type); + return; + } + + alert.OnClick?.AlertClicked(player.Value); + } + + private static void ClientAlertsGetState(EntityUid uid, AlertsComponent component, ref ComponentGetState args) + { + args.State = new AlertsComponentState(component.Alerts); + } +} diff --git a/Content.Shared/Alert/ClickAlertEvent.cs b/Content.Shared/Alert/ClickAlertEvent.cs new file mode 100644 index 0000000000..b7a5a8e305 --- /dev/null +++ b/Content.Shared/Alert/ClickAlertEvent.cs @@ -0,0 +1,19 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.Alert; + +/// +/// A message that calls the click interaction on a alert +/// +[Serializable, NetSerializable] +public class ClickAlertEvent : EntityEventArgs +{ + public readonly AlertType Type; + + public ClickAlertEvent(AlertType alertType) + { + Type = alertType; + } +} \ No newline at end of file diff --git a/Content.Shared/Alert/IAlertClick.cs b/Content.Shared/Alert/IAlertClick.cs index e953b6f525..a6287a22c3 100644 --- a/Content.Shared/Alert/IAlertClick.cs +++ b/Content.Shared/Alert/IAlertClick.cs @@ -1,5 +1,4 @@ -using System; -using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects; namespace Content.Shared.Alert { @@ -11,25 +10,7 @@ namespace Content.Shared.Alert /// /// Invoked on server side when user clicks an alert. /// - /// - void AlertClicked(ClickAlertEventArgs args); - } - - public class ClickAlertEventArgs : EventArgs - { - /// - /// Player clicking the alert - /// - public readonly EntityUid Player; - /// - /// Alert that was clicked - /// - public readonly AlertPrototype Alert; - - public ClickAlertEventArgs(EntityUid player, AlertPrototype alert) - { - Player = player; - Alert = alert; - } + /// + void AlertClicked(EntityUid player); } } diff --git a/Content.Shared/Alert/SharedAlertsComponent.cs b/Content.Shared/Alert/SharedAlertsComponent.cs deleted file mode 100644 index db7859a482..0000000000 --- a/Content.Shared/Alert/SharedAlertsComponent.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Players; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Shared.Alert -{ - /// - /// Handles the icons on the right side of the screen. - /// Should only be used for player-controlled entities. - /// - [NetworkedComponent()] - public abstract class SharedAlertsComponent : Component - { - [Dependency] - protected readonly AlertManager AlertManager = default!; - - public override string Name => "Alerts"; - - [ViewVariables] private Dictionary _alerts = new(); - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not AlertsComponentState state) - { - return; - } - - _alerts = state.Alerts; - } - - public override ComponentState GetComponentState() - { - return new AlertsComponentState(_alerts); - } - - /// true iff an alert of the indicated alert category is currently showing - public bool IsShowingAlertCategory(AlertCategory alertCategory) - { - return IsShowingAlert(AlertKey.ForCategory(alertCategory)); - } - - /// true iff an alert of the indicated id is currently showing - public bool IsShowingAlert(AlertType alertType) - { - if (AlertManager.TryGet(alertType, out var alert)) - { - return IsShowingAlert(alert.AlertKey); - } - Logger.DebugS("alert", "unknown alert type {0}", alertType); - return false; - - } - - /// true iff an alert of the indicated key is currently showing - protected bool IsShowingAlert(AlertKey alertKey) - { - return _alerts.ContainsKey(alertKey); - } - - protected IEnumerable> EnumerateAlertStates() - { - return _alerts; - } - - protected bool TryGetAlertState(AlertKey key, out AlertState alertState) - { - return _alerts.TryGetValue(key, out alertState); - } - - /// - /// Shows the alert. If the alert or another alert of the same category is already showing, - /// it will be updated / replaced with the specified values. - /// - /// type of the alert to set - /// severity, if supported by the alert - /// cooldown start and end, if null there will be no cooldown (and it will - /// be erased if there is currently a cooldown for the alert) - public void ShowAlert(AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null) - { - if (AlertManager.TryGet(alertType, out var alert)) - { - // Check whether the alert category we want to show is already being displayed, with the same type, - // severity, and cooldown. - if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) && - alertStateCallback.Type == alertType && - alertStateCallback.Severity == severity && - alertStateCallback.Cooldown == cooldown) - { - return; - } - - // In the case we're changing the alert type but not the category, we need to remove it first. - _alerts.Remove(alert.AlertKey); - - _alerts[alert.AlertKey] = new AlertState - {Cooldown = cooldown, Severity = severity, Type=alertType}; - - AfterShowAlert(); - - Dirty(); - - } - else - { - Logger.ErrorS("alert", "Unable to show alert {0}, please ensure this alertType has" + - " a corresponding YML alert prototype", - alertType); - } - } - - /// - /// Clear the alert with the given category, if one is currently showing. - /// - public void ClearAlertCategory(AlertCategory category) - { - var key = AlertKey.ForCategory(category); - if (!_alerts.Remove(key)) - { - return; - } - - AfterClearAlert(); - - Dirty(); - } - - /// - /// Clear the alert of the given type if it is currently showing. - /// - public void ClearAlert(AlertType alertType) - { - if (AlertManager.TryGet(alertType, out var alert)) - { - if (!_alerts.Remove(alert.AlertKey)) - { - return; - } - - AfterClearAlert(); - - Dirty(); - } - else - { - Logger.ErrorS("alert", "unable to clear alert, unknown alertType {0}", alertType); - } - - } - - /// - /// Invoked after showing an alert prior to dirtying the component - /// - protected virtual void AfterShowAlert() { } - - /// - /// Invoked after clearing an alert prior to dirtying the component - /// - protected virtual void AfterClearAlert() { } - } - - [Serializable, NetSerializable] - public class AlertsComponentState : ComponentState - { - public Dictionary Alerts; - - public AlertsComponentState(Dictionary alerts) - { - Alerts = alerts; - } - } - - /// - /// A message that calls the click interaction on a alert - /// - [Serializable, NetSerializable] -#pragma warning disable 618 - public class ClickAlertMessage : ComponentMessage -#pragma warning restore 618 - { - public readonly AlertType Type; - - public ClickAlertMessage(AlertType alertType) - { - Directed = true; - Type = alertType; - } - } - - [Serializable, NetSerializable] - public struct AlertState - { - public short? Severity; - public (TimeSpan, TimeSpan)? Cooldown; - public AlertType Type; - } -} diff --git a/Content.Shared/Jittering/SharedJitteringSystem.cs b/Content.Shared/Jittering/SharedJitteringSystem.cs index 7a5757d2c9..7bd02f28f4 100644 --- a/Content.Shared/Jittering/SharedJitteringSystem.cs +++ b/Content.Shared/Jittering/SharedJitteringSystem.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using Content.Shared.Alert; using Content.Shared.StatusEffect; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; @@ -58,10 +56,8 @@ namespace Content.Shared.Jittering /// Frequency for jittering. See and . /// Whether to change any existing jitter value even if they're greater than the ones we're setting. /// The status effects component to modify. - /// The alerts component. public void DoJitter(EntityUid uid, TimeSpan time, bool refresh, float amplitude = 10f, float frequency = 4f, bool forceValueChange = false, - StatusEffectsComponent? status = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? status = null) { if (!Resolve(uid, ref status, false)) return; @@ -69,7 +65,7 @@ namespace Content.Shared.Jittering amplitude = Math.Clamp(amplitude, MinAmplitude, MaxAmplitude); frequency = Math.Clamp(frequency, MinFrequency, MaxFrequency); - if (StatusEffects.TryAddStatusEffect(uid, "Jitter", time, refresh, status, alerts)) + if (StatusEffects.TryAddStatusEffect(uid, "Jitter", time, refresh, status)) { var jittering = EntityManager.GetComponent(uid); diff --git a/Content.Shared/MobState/Components/MobStateComponent.cs b/Content.Shared/MobState/Components/MobStateComponent.cs index 90f9dfeaed..82756b497c 100644 --- a/Content.Shared/MobState/Components/MobStateComponent.cs +++ b/Content.Shared/MobState/Components/MobStateComponent.cs @@ -10,7 +10,6 @@ using Content.Shared.MobState.State; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; -using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -69,10 +68,7 @@ namespace Content.Shared.MobState.Components protected override void OnRemove() { - if (_entMan.TryGetComponent(Owner, out SharedAlertsComponent? status)) - { - status.ClearAlert(AlertType.HumanHealth); - } + EntitySystem.Get().ClearAlert(Owner, AlertType.HumanHealth); base.OnRemove(); } diff --git a/Content.Shared/MobState/State/SharedCriticalMobState.cs b/Content.Shared/MobState/State/SharedCriticalMobState.cs index 4da171f79a..c0a4e03609 100644 --- a/Content.Shared/MobState/State/SharedCriticalMobState.cs +++ b/Content.Shared/MobState/State/SharedCriticalMobState.cs @@ -1,4 +1,4 @@ -using Content.Shared.Alert; +using Content.Shared.Alert; using Content.Shared.Standing; using Robust.Shared.GameObjects; @@ -15,10 +15,7 @@ namespace Content.Shared.MobState.State { base.EnterState(uid, entityManager); - if (entityManager.TryGetComponent(uid, out SharedAlertsComponent? status)) - { - status.ShowAlert(AlertType.HumanCrit); // TODO: combine humancrit-0 and humancrit-1 into a gif and display it - } + EntitySystem.Get().ShowAlert(uid, AlertType.HumanCrit); // TODO: combine humancrit-0 and humancrit-1 into a gif and display it EntitySystem.Get().Down(uid); diff --git a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs index d69fca7773..10ff273900 100644 --- a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs +++ b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs @@ -14,6 +14,7 @@ namespace Content.Shared.Pulling.Systems { [Dependency] private readonly SharedPullingSystem _pullSystem = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; public override void Initialize() { @@ -47,8 +48,7 @@ namespace Content.Shared.Pulling.Systems if (args.Puller.Owner != uid) return; - if (EntityManager.TryGetComponent(component.Owner, out SharedAlertsComponent? alerts)) - alerts.ShowAlert(AlertType.Pulling); + _alertsSystem.ShowAlert(component.Owner, AlertType.Pulling); RefreshMovementSpeed(component); } @@ -61,8 +61,8 @@ namespace Content.Shared.Pulling.Systems if (args.Puller.Owner != uid) return; - if (EntityManager.TryGetComponent(component.Owner, out SharedAlertsComponent? alerts)) - alerts.ClearAlert(AlertType.Pulling); + var euid = component.Owner; + _alertsSystem.ClearAlert(euid, AlertType.Pulling); RefreshMovementSpeed(component); } diff --git a/Content.Shared/Pulling/Systems/SharedPullingSystem.cs b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs index 5223200464..ca98198be9 100644 --- a/Content.Shared/Pulling/Systems/SharedPullingSystem.cs +++ b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs @@ -25,6 +25,7 @@ namespace Content.Shared.Pulling public abstract partial class SharedPullingSystem : EntitySystem { [Dependency] private readonly SharedPullingStateManagementSystem _pullSm = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; /// /// A mapping of pullers to the entity that they are pulling. @@ -105,9 +106,8 @@ namespace Content.Shared.Pulling { if (args.Pulled.Owner != uid) return; - - if (EntityManager.TryGetComponent(component.Owner, out SharedAlertsComponent? alerts)) - alerts.ShowAlert(AlertType.Pulled); + + _alertsSystem.ShowAlert(component.Owner, AlertType.Pulled); } private void PullableHandlePullStopped(EntityUid uid, SharedPullableComponent component, PullStoppedMessage args) @@ -115,8 +115,7 @@ namespace Content.Shared.Pulling if (args.Pulled.Owner != uid) return; - if (EntityManager.TryGetComponent(component.Owner, out SharedAlertsComponent? alerts)) - alerts.ClearAlert(AlertType.Pulled); + _alertsSystem.ClearAlert(component.Owner, AlertType.Pulled); } public override void Update(float frameTime) diff --git a/Content.Shared/Speech/EntitySystems/SharedStutteringSystem.cs b/Content.Shared/Speech/EntitySystems/SharedStutteringSystem.cs index a5b63bab40..5d00175e98 100644 --- a/Content.Shared/Speech/EntitySystems/SharedStutteringSystem.cs +++ b/Content.Shared/Speech/EntitySystems/SharedStutteringSystem.cs @@ -1,5 +1,4 @@ using System; -using Content.Shared.Alert; using Content.Shared.StatusEffect; using Robust.Shared.GameObjects; @@ -8,7 +7,7 @@ namespace Content.Shared.Speech.EntitySystems public abstract class SharedStutteringSystem : EntitySystem { // For code in shared... I imagine we ain't getting accent prediction anytime soon so let's not bother. - public virtual void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null, SharedAlertsComponent? alerts = null) + public virtual void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null) { } } diff --git a/Content.Shared/StatusEffect/StatusEffectsSystem.cs b/Content.Shared/StatusEffect/StatusEffectsSystem.cs index db296706c1..b0982f91c8 100644 --- a/Content.Shared/StatusEffect/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffect/StatusEffectsSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; using Content.Shared.Alert; using Robust.Shared.GameObjects; @@ -15,6 +15,7 @@ namespace Content.Shared.StatusEffect [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; public override void Initialize() { @@ -80,20 +81,16 @@ namespace Content.Shared.StatusEffect /// How long the effect should last for. /// The status effect cooldown should be refreshed (true) or accumulated (false). /// The status effects component to change, if you already have it. - /// The alerts component to modify, if the status effect has an alert. /// False if the effect could not be added or the component already exists, true otherwise. /// The component type to add and remove from the entity. public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, - StatusEffectsComponent? status=null, - SharedAlertsComponent? alerts=null) + StatusEffectsComponent? status = null) where T: Component, new() { if (!Resolve(uid, ref status, false)) return false; - Resolve(uid, ref alerts, false); - - if (TryAddStatusEffect(uid, key, time, refresh, status, alerts)) + if (TryAddStatusEffect(uid, key, time, refresh, status)) { // If they already have the comp, we just won't bother updating anything. if (!EntityManager.HasComponent(uid)) @@ -108,15 +105,12 @@ namespace Content.Shared.StatusEffect } public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component, - StatusEffectsComponent? status = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? status = null) { if (!Resolve(uid, ref status, false)) return false; - Resolve(uid, ref alerts, false); - - if (TryAddStatusEffect(uid, key, time, refresh, status, alerts)) + if (TryAddStatusEffect(uid, key, time, refresh, status)) { // If they already have the comp, we just won't bother updating anything. if (!EntityManager.HasComponent(uid, _componentFactory.GetRegistration(component).Type)) @@ -142,26 +136,22 @@ namespace Content.Shared.StatusEffect /// How long the effect should last for. /// The status effect cooldown should be refreshed (true) or accumulated (false). /// The status effects component to change, if you already have it. - /// The alerts component to modify, if the status effect has an alert. /// False if the effect could not be added, or if the effect already existed. /// /// This obviously does not add any actual 'effects' on its own. Use the generic overload, /// which takes in a component type, if you want to automatically add and remove a component. - /// + /// /// If the effect already exists, it will simply replace the cooldown with the new one given. /// If you want special 'effect merging' behavior, do it your own damn self! /// public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, - StatusEffectsComponent? status=null, - SharedAlertsComponent? alerts=null) + StatusEffectsComponent? status=null) { if (!Resolve(uid, ref status, false)) return false; if (!CanApplyEffect(uid, key, status)) return false; - Resolve(uid, ref alerts, false); - // we already checked if it has the index in CanApplyEffect so a straight index and not tryindex here // is fine var proto = _prototypeManager.Index(key); @@ -191,9 +181,10 @@ namespace Content.Shared.StatusEffect status.ActiveEffects.Add(key, new StatusEffectState(cooldown, refresh, null)); } - if (proto.Alert != null && alerts != null) + if (proto.Alert != null) { - alerts.ShowAlert(proto.Alert.Value, cooldown: GetAlertCooldown(uid, proto.Alert.Value, status)); + var cooldown1 = GetAlertCooldown(uid, proto.Alert.Value, status); + _alertsSystem.ShowAlert(uid, proto.Alert.Value, null, cooldown1); } status.Dirty(); @@ -233,15 +224,13 @@ namespace Content.Shared.StatusEffect /// The entity to remove an effect from. /// The effect ID to remove. /// The status effects component to change, if you already have it. - /// The alerts component to modify, if the status effect has an alert. /// False if the effect could not be removed, true otherwise. /// /// Obviously this doesn't automatically clear any effects a status effect might have. /// That's up to the removed component to handle itself when it's removed. /// public bool TryRemoveStatusEffect(EntityUid uid, string key, - StatusEffectsComponent? status=null, - SharedAlertsComponent? alerts=null) + StatusEffectsComponent? status=null) { if (!Resolve(uid, ref status, false)) return false; @@ -250,8 +239,6 @@ namespace Content.Shared.StatusEffect if (!_prototypeManager.TryIndex(key, out var proto)) return false; - Resolve(uid, ref alerts, false); - var state = status.ActiveEffects[key]; // There are cases where a status effect component might be server-only, so TryGetRegistration... @@ -267,9 +254,9 @@ namespace Content.Shared.StatusEffect EntityManager.RemoveComponent(uid, type); } - if (proto.Alert != null && alerts != null) + if (proto.Alert != null) { - alerts.ClearAlert(proto.Alert.Value); + _alertsSystem.ClearAlert(uid, proto.Alert.Value); } status.ActiveEffects.Remove(key); @@ -284,21 +271,17 @@ namespace Content.Shared.StatusEffect /// /// The entity to remove effects from. /// The status effects component to change, if you already have it. - /// The alerts component to modify, if the status effect has an alert. /// False if any status effects failed to be removed, true if they all did. public bool TryRemoveAllStatusEffects(EntityUid uid, - StatusEffectsComponent? status = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? status = null) { if (!Resolve(uid, ref status, false)) return false; - Resolve(uid, ref alerts, false); - bool failed = false; foreach (var effect in status.ActiveEffects) { - if(!TryRemoveStatusEffect(uid, effect.Key, status, alerts)) + if(!TryRemoveStatusEffect(uid, effect.Key, status)) failed = true; } @@ -350,14 +333,11 @@ namespace Content.Shared.StatusEffect /// The amount of time to add. /// The status effect component, should you already have it. public bool TryAddTime(EntityUid uid, string key, TimeSpan time, - StatusEffectsComponent? status=null, - SharedAlertsComponent? alert=null) + StatusEffectsComponent? status=null) { if (!Resolve(uid, ref status, false)) return false; - Resolve(uid, ref alert, false); - if (!HasStatusEffect(uid, key, status)) return false; @@ -366,11 +346,10 @@ namespace Content.Shared.StatusEffect status.ActiveEffects[key].Cooldown = timer; if (_prototypeManager.TryIndex(key, out var proto) - && alert != null && proto.Alert != null) { - alert.ShowAlert(proto.Alert.Value, cooldown: GetAlertCooldown(uid, proto.Alert.Value, status)); - + (TimeSpan, TimeSpan)? cooldown = GetAlertCooldown(uid, proto.Alert.Value, status); + _alertsSystem.ShowAlert(uid, proto.Alert.Value, null, cooldown); } return true; @@ -384,14 +363,11 @@ namespace Content.Shared.StatusEffect /// The amount of time to add. /// The status effect component, should you already have it. public bool TryRemoveTime(EntityUid uid, string key, TimeSpan time, - StatusEffectsComponent? status=null, - SharedAlertsComponent? alert=null) + StatusEffectsComponent? status=null) { if (!Resolve(uid, ref status, false)) return false; - Resolve(uid, ref alert, false); - if (!HasStatusEffect(uid, key, status)) return false; @@ -405,11 +381,10 @@ namespace Content.Shared.StatusEffect status.ActiveEffects[key].Cooldown = timer; if (_prototypeManager.TryIndex(key, out var proto) - && alert != null && proto.Alert != null) { - alert.ShowAlert(proto.Alert.Value, cooldown: GetAlertCooldown(uid, proto.Alert.Value, status)); - + (TimeSpan, TimeSpan)? cooldown = GetAlertCooldown(uid, proto.Alert.Value, status); + _alertsSystem.ShowAlert(uid, proto.Alert.Value, null, cooldown); } return true; diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index c69e5c8245..aac907f929 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -1,5 +1,4 @@ using System; -using Content.Shared.Alert; using Content.Shared.Audio; using Content.Shared.DragDrop; using Content.Shared.Interaction; @@ -119,8 +118,7 @@ namespace Content.Shared.Stunnable /// Stuns the entity, disallowing it from doing many interactions temporarily. /// public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, - StatusEffectsComponent? status = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? status = null) { if (time <= TimeSpan.Zero) return false; @@ -128,17 +126,14 @@ namespace Content.Shared.Stunnable if (!Resolve(uid, ref status, false)) return false; - Resolve(uid, ref alerts, false); - - return _statusEffectSystem.TryAddStatusEffect(uid, "Stun", time, refresh, alerts: alerts); + return _statusEffectSystem.TryAddStatusEffect(uid, "Stun", time, refresh); } /// /// Knocks down the entity, making it fall to the ground. /// public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, - StatusEffectsComponent? status = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? status = null) { if (time <= TimeSpan.Zero) return false; @@ -146,25 +141,19 @@ namespace Content.Shared.Stunnable if (!Resolve(uid, ref status, false)) return false; - Resolve(uid, ref alerts, false); - - return _statusEffectSystem.TryAddStatusEffect(uid, "KnockedDown", time, refresh, alerts: alerts); + return _statusEffectSystem.TryAddStatusEffect(uid, "KnockedDown", time, refresh); } /// /// Applies knockdown and stun to the entity temporarily. /// public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh, - StatusEffectsComponent? status = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? status = null) { if (!Resolve(uid, ref status)) return false; - // Optional component. - Resolve(uid, ref alerts, false); - - return TryKnockdown(uid, time, refresh, status, alerts) && TryStun(uid, time, refresh, status, alerts); + return TryKnockdown(uid, time, refresh, status) && TryStun(uid, time, refresh, status); } /// @@ -172,19 +161,15 @@ namespace Content.Shared.Stunnable /// public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh, float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f, - StatusEffectsComponent? status = null, - SharedAlertsComponent? alerts = null) + StatusEffectsComponent? status = null) { if (!Resolve(uid, ref status)) return false; - // "Optional" component. - Resolve(uid, ref alerts, false); - if (time <= TimeSpan.Zero) return false; - if (_statusEffectSystem.TryAddStatusEffect(uid, "SlowedDown", time, refresh, status, alerts)) + if (_statusEffectSystem.TryAddStatusEffect(uid, "SlowedDown", time, refresh, status)) { var slowed = EntityManager.GetComponent(uid); // Doesn't make much sense to have the "TrySlowdown" method speed up entities now does it? diff --git a/Content.Tests/Shared/Alert/AlertManagerTests.cs b/Content.Tests/Shared/Alert/AlertManagerTests.cs index c56e12c588..ec6108f1b0 100644 --- a/Content.Tests/Shared/Alert/AlertManagerTests.cs +++ b/Content.Tests/Shared/Alert/AlertManagerTests.cs @@ -1,14 +1,17 @@ using System.IO; using Content.Shared.Alert; using NUnit.Framework; +using NUnit.Framework.Interfaces; +using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Prototypes; +using Robust.Shared.Reflection; using Robust.Shared.Serialization.Manager; using Robust.Shared.Utility; namespace Content.Tests.Shared.Alert { - [TestFixture, TestOf(typeof(AlertManager))] + [TestFixture, TestOf(typeof(AlertsSystem))] public class AlertManagerTests : ContentUnitTest { const string PROTOTYPES = @" @@ -24,23 +27,26 @@ namespace Content.Tests.Shared.Alert "; [Test] + [Ignore("There is no way to load extra Systems in a unit test, fixing RobustUnitTest is out of scope.")] public void TestAlertManager() { IoCManager.Resolve().Initialize(); + + var reflection = IoCManager.Resolve(); + reflection.LoadAssemblies(); + var prototypeManager = IoCManager.Resolve(); prototypeManager.Initialize(); prototypeManager.LoadFromStream(new StringReader(PROTOTYPES)); - var alertManager = IoCManager.Resolve(); - alertManager.Initialize(); - Assert.That(alertManager.TryGet(AlertType.LowPressure, out var lowPressure)); + Assert.That(EntitySystem.Get().TryGet(AlertType.LowPressure, out var lowPressure)); Assert.That(lowPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); - Assert.That(alertManager.TryGet(AlertType.HighPressure, out var highPressure)); + Assert.That(EntitySystem.Get().TryGet(AlertType.HighPressure, out var highPressure)); Assert.That(highPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/highpressure.png")))); - Assert.That(alertManager.TryGet(AlertType.LowPressure, out lowPressure)); + Assert.That(EntitySystem.Get().TryGet(AlertType.LowPressure, out lowPressure)); Assert.That(lowPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); - Assert.That(alertManager.TryGet(AlertType.HighPressure, out highPressure)); + Assert.That(EntitySystem.Get().TryGet(AlertType.HighPressure, out highPressure)); Assert.That(highPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/highpressure.png")))); } } diff --git a/Content.Tests/Server/GameObjects/Components/Mobs/ServerAlertsComponentTests.cs b/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs similarity index 66% rename from Content.Tests/Server/GameObjects/Components/Mobs/ServerAlertsComponentTests.cs rename to Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs index e6016b1476..87c57ab57d 100644 --- a/Content.Tests/Server/GameObjects/Components/Mobs/ServerAlertsComponentTests.cs +++ b/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using Content.Server.Alert; using Content.Shared.Alert; using NUnit.Framework; @@ -7,10 +8,10 @@ using Robust.Shared.IoC; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; -namespace Content.Tests.Server.GameObjects.Components.Mobs +namespace Content.Tests.Shared.Alert { [TestFixture] - [TestOf(typeof(ServerAlertsComponent))] + [TestOf(typeof(AlertsComponent))] public class ServerAlertsComponentTests : ContentUnitTest { const string PROTOTYPES = @" @@ -28,6 +29,7 @@ namespace Content.Tests.Server.GameObjects.Components.Mobs "; [Test] + [Ignore("There is no way to load extra Systems in a unit test, fixing RobustUnitTest is out of scope.")] public void ShowAlerts() { // this is kind of unnecessary because there's integration test coverage of Alert components @@ -38,31 +40,31 @@ namespace Content.Tests.Server.GameObjects.Components.Mobs var prototypeManager = IoCManager.Resolve(); prototypeManager.Initialize(); var factory = IoCManager.Resolve(); - factory.RegisterClass(); + factory.RegisterClass(); prototypeManager.LoadFromStream(new StringReader(PROTOTYPES)); prototypeManager.Resync(); - var alertManager = IoCManager.Resolve(); - alertManager.Initialize(); + var entSys = IoCManager.Resolve(); + entSys.LoadExtraSystemType(); - var alertsComponent = new ServerAlertsComponent(); + var alertsComponent = new AlertsComponent(); alertsComponent = IoCManager.InjectDependencies(alertsComponent); - Assert.That(alertManager.TryGet(AlertType.LowPressure, out var lowpressure)); - Assert.That(alertManager.TryGet(AlertType.HighPressure, out var highpressure)); + Assert.That(EntitySystem.Get().TryGet(AlertType.LowPressure, out var lowpressure)); + Assert.That(EntitySystem.Get().TryGet(AlertType.HighPressure, out var highpressure)); - alertsComponent.ShowAlert(AlertType.LowPressure); + EntitySystem.Get().ShowAlert(alertsComponent.Owner, AlertType.LowPressure, null, null); var alertState = alertsComponent.GetComponentState() as AlertsComponentState; Assert.NotNull(alertState); Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); Assert.That(alertState.Alerts.ContainsKey(lowpressure.AlertKey)); - alertsComponent.ShowAlert(AlertType.HighPressure); + EntitySystem.Get().ShowAlert(alertsComponent.Owner, AlertType.HighPressure, null, null); alertState = alertsComponent.GetComponentState() as AlertsComponentState; Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); Assert.That(alertState.Alerts.ContainsKey(highpressure.AlertKey)); - alertsComponent.ClearAlertCategory(AlertCategory.Pressure); + EntitySystem.Get().ClearAlertCategory(alertsComponent.Owner, AlertCategory.Pressure); alertState = alertsComponent.GetComponentState() as AlertsComponentState; Assert.That(alertState.Alerts.Count, Is.EqualTo(0)); }