ECSatize AlertsSystem (#5559)

This commit is contained in:
Acruid
2022-01-05 00:19:23 -08:00
committed by GitHub
parent 36d4de5e61
commit 5b1cd2dd96
59 changed files with 1069 additions and 1038 deletions

View File

@@ -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
{
/// <inheritdoc/>
[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<AlertKey, AlertControl> _alertControls
= new();
/// <summary>
/// Allows calculating if we need to act due to this component being controlled by the current mob
/// </summary>
[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<IPrototypeManager>().EnumeratePrototypes<AlertOrderPrototype>().FirstOrDefault();
if (_alertOrder == null)
{
Logger.ErrorS("alert", "no alertOrder prototype found, alerts will be in random order");
}
_ui = new AlertsUI();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_ui);
UpdateAlertsControls();
}
public void PlayerDetached()
{
foreach (var alertControl in _alertControls.Values)
{
alertControl.OnPressed -= AlertControlOnPressed;
}
if (_ui != null)
{
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_ui);
_ui = null;
}
_alertControls.Clear();
}
/// <summary>
/// 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)
/// </summary>
private void UpdateAlertsControls()
{
if (!CurrentlyControlled || _ui == null)
{
return;
}
// remove any controls with keys no longer present
var toRemove = new List<AlertKey>();
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();
}
}
}

View File

@@ -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 AlertOrderPrototype? AlertOrder { get; set; }
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public event EventHandler? ClearAlerts;
public event EventHandler<IReadOnlyDictionary<AlertKey, AlertState>>? SyncAlerts;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ClientAlertsComponent, PlayerAttachedEvent>((_, component, _) => component.PlayerAttached());
SubscribeLocalEvent<ClientAlertsComponent, PlayerDetachedEvent>((_, component, _) => component.PlayerDetached());
SubscribeLocalEvent<AlertsComponent, PlayerAttachedEvent>((_, component, _) => PlayerAttached(component));
SubscribeLocalEvent<AlertsComponent, PlayerDetachedEvent>((_, _, _) => PlayerDetached());
SubscribeLocalEvent<AlertsComponent, ComponentHandleState>(ClientAlertsHandleState);
}
protected override void LoadPrototypes()
{
base.LoadPrototypes();
AlertOrder = _prototypeManager.EnumeratePrototypes<AlertOrderPrototype>().FirstOrDefault();
if (AlertOrder == null)
Logger.ErrorS("alert", "no alertOrder prototype found, alerts will be in random order");
}
public IReadOnlyDictionary<AlertKey, AlertState>? 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));
}
/// <summary>
/// Allows calculating if we need to act due to this component being controlled by the current mob
/// </summary>
private static bool CurControlled(EntityUid entity, IPlayerManager playerManager)
{
return playerManager.LocalPlayer != null && playerManager.LocalPlayer.ControlledEntity == entity;
}
}

View File

@@ -1,26 +1,162 @@
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
{
/// <summary>
/// The status effects display on the right side of the screen.
/// </summary>
[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!;
public const float ChatSeparation = 38f;
private IAlertsFrameView _alertsFrame;
private ClientAlertsSystem? _alertsSystem;
public AlertsUI()
public AlertsFramePresenter()
{
// This is a lot easier than a factory
IoCManager.InjectDependencies(this);
_alertsFrame = new AlertsUI(_chatManager);
_userInterfaceManager.StateRoot.AddChild((AlertsUI) _alertsFrame);
// This is required so that if we load after the system is initialized, we can bind to it immediately
if (_systemManager.TryGetEntitySystem<ClientAlertsSystem>(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)
{
SystemOnSyncAlerts(alertsSystem, alerts);
}
}
/// <inheritdoc />
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<AlertKey, AlertState> 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)
{
if (_alertsSystem is null)
return;
UnbindFromSystem();
}
else
{
if (_alertsSystem is null)
{
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
}
/// <summary>
/// This is the frame of vertical set of alerts that show up on the HUD.
/// </summary>
public interface IAlertsFrameView : IDisposable
{
event EventHandler<AlertType>? AlertPressed;
void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
IReadOnlyDictionary<AlertKey, AlertState> alertStates);
void ClearAllControls();
}
/// <summary>
/// The status effects display on the right side of the screen.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class AlertsUI : Control, IAlertsFrameView
{
// also known as Control.Children?
private readonly Dictionary<AlertKey, AlertControl> _alertControls = new();
public AlertsUI(IChatManager chatManager)
{
_chatManager = chatManager;
RobustXamlLoader.Load(this);
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
@@ -33,6 +169,40 @@ namespace Content.Client.Alerts.UI
LayoutContainer.SetMarginRight(this, -10);
}
public void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
IReadOnlyDictionary<AlertKey, AlertState> 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<AlertType>? 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();
@@ -48,19 +218,16 @@ namespace Content.Client.Alerts.UI
private void OnChatResized(ChatResizedEventArgs chatResizedEventArgs)
{
// resize us to fit just below the chatbox
// 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,
@@ -75,5 +242,103 @@ namespace Content.Client.Alerts.UI
AlertContainer.MaxGridHeight = Height;
base.UIScaleChanged();
}
private bool SyncRemoveControls(IReadOnlyDictionary<AlertKey, AlertState> alertStates)
{
var toRemove = new List<AlertKey>();
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<AlertKey, AlertState> 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
{
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;
}
}
}
private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState)
{
var alertControl = new AlertControl(alert, alertState.Severity)
{
Cooldown = alertState.Cooldown
};
alertControl.OnPressed += AlertControlPressed;
return alertControl;
}
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);
}
}

View File

@@ -190,7 +190,6 @@ namespace Content.Client.Entry
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
IoCManager.Resolve<IStationEventManager>().Initialize();
IoCManager.Resolve<EuiManager>().Initialize();
IoCManager.Resolve<AlertManager>().Initialize();
IoCManager.Resolve<ActionManager>().Initialize();
IoCManager.Resolve<IVoteManager>().Initialize();
IoCManager.Resolve<IGamePrototypeLoadManager>().Initialize();

View File

@@ -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<IScreenshotHook, ScreenshotHook>();
IoCManager.Register<IClickMapManager, ClickMapManager>();
IoCManager.Register<IStationEventManager, StationEventManager>();
IoCManager.Register<AlertManager, AlertManager>();
IoCManager.Register<ActionManager, ActionManager>();
IoCManager.Register<IClientAdminManager, ClientAdminManager>();
IoCManager.Register<EuiManager, EuiManager>();

View File

@@ -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
/// </summary>
private void SetupPresenters()
{
// HUD
_alertsFramePresenter = new AlertsFramePresenter();
// Windows
_constructionMenu = new ConstructionMenuPresenter(_gameHud);
}
@@ -115,7 +120,11 @@ namespace Content.Client.Viewport
/// </summary>
private void DisposePresenters()
{
// Windows
_constructionMenu?.Dispose();
// HUD
_alertsFramePresenter?.Dispose();
}
internal static void FocusChat(ChatBox chat)

View File

@@ -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<IPlayerManager>();
var alertsSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<AlertsSystem>();
await server.WaitAssertion(() =>
{
var playerEnt = serverPlayerManager.Sessions.Single().AttachedEntity.GetValueOrDefault();
Assert.That(playerEnt != default);
var alertsComponent = IoCManager.Resolve<IEntityManager>().GetComponent<ServerAlertsComponent>(playerEnt);
var alertsComponent = IoCManager.Resolve<IEntityManager>().GetComponent<AlertsComponent>(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<IEntityManager>().GetComponent<ClientAlertsComponent>(controlled.Value);
var alertsComponent = IoCManager.Resolve<IEntityManager>().GetComponent<AlertsComponent>(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<IEntityManager>().GetComponent<ServerAlertsComponent>(playerEnt);
var alertsComponent = IoCManager.Resolve<IEntityManager>().GetComponent<AlertsComponent>(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<IEntityManager>().GetComponent<ClientAlertsComponent>(controlled.Value);
var alertsComponent = IoCManager.Resolve<IEntityManager>().GetComponent<AlertsComponent>(controlled.Value);
Assert.NotNull(alertsComponent);
// find the alertsui

View File

@@ -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<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var alertsSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<AlertsSystem>();
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<TransformComponent>(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.
/*

View File

@@ -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<IEntityManager>().TryGetComponent(args.Player, out CuffableComponent? cuffableComponent))
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(player, out CuffableComponent? cuffableComponent))
{
cuffableComponent.TryUncuff(args.Player);
cuffableComponent.TryUncuff(player);
}
}
}

View File

@@ -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<IEntityManager>().TryGetComponent(args.Player, out FlammableComponent? flammable))
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(player, out FlammableComponent? flammable))
{
EntitySystem.Get<FlammableSystem>().Resist(args.Player, flammable);
EntitySystem.Get<FlammableSystem>().Resist(player, flammable);
}
}
}

View File

@@ -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<ActionBlockerSystem>().CanInteract(args.Player))
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(player))
return;
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<SharedPullableComponent?>(args.Player, out var playerPullable))
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<SharedPullableComponent?>(player, out var playerPullable))
{
EntitySystem.Get<SharedPullingSystem>().TryStopPull(playerPullable);
}

View File

@@ -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<IEntityManager>().TryGetComponent(args.Player, out PilotComponent? pilotComponent) &&
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(player, out PilotComponent? pilotComponent) &&
pilotComponent.Console != null)
{
EntitySystem.Get<ShuttleConsoleSystem>().RemovePilot(pilotComponent);

View File

@@ -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<SharedPullingSystem>();
var playerTarget = ps.GetPulled(args.Player);
var playerTarget = ps.GetPulled(player);
if (playerTarget != default && IoCManager.Resolve<IEntityManager>().TryGetComponent(playerTarget, out SharedPullableComponent playerPullable))
{
ps.TryStopPull(playerPullable);

View File

@@ -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<IEntityManager>().TryGetComponent(args.Player, out BuckleComponent? buckle))
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(player, out BuckleComponent? buckle))
{
buckle.TryUnbuckle(args.Player);
buckle.TryUnbuckle(player);
}
}
}

View File

@@ -34,21 +34,21 @@ namespace Content.Server.Alert.Commands
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(attachedEntity, out ServerAlertsComponent? alertsComponent))
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(attachedEntity, out AlertsComponent? alertsComponent))
{
shell.WriteLine("user has no alerts component");
return;
}
var alertType = args[0];
var alertMgr = IoCManager.Resolve<AlertManager>();
if (!alertMgr.TryGet(Enum.Parse<AlertType>(alertType), out var alert))
var alertsSystem = EntitySystem.Get<AlertsSystem>();
if (!alertsSystem.TryGet(Enum.Parse<AlertType>(alertType), out var alert))
{
shell.WriteLine("unrecognized alertType " + alertType);
return;
}
alertsComponent.ClearAlert(alert.AlertType);
alertsSystem.ClearAlert(attachedEntity, alert.AlertType);
}
}
}

View File

@@ -34,7 +34,7 @@ namespace Content.Server.Alert.Commands
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
}
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(attachedEntity, out ServerAlertsComponent? alertsComponent))
if (!IoCManager.Resolve<IEntityManager>().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<AlertManager>();
if (!alertMgr.TryGet(Enum.Parse<AlertType>(alertType), out var alert))
var alertsSystem = EntitySystem.Get<AlertsSystem>();
if (!alertsSystem.TryGet(Enum.Parse<AlertType>(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);
}
}
}

View File

@@ -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<WeightlessSystem>(out var weightlessSystem))
{
weightlessSystem.AddAlert(this);
}
else
{
Logger.WarningS("alert", "weightlesssystem not found");
}
}
protected override void OnRemove()
{
if (EntitySystem.TryGet<WeightlessSystem>(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<IEntityManager>().GetComponent<MetaDataComponent>(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;
}
}
}
}
}

View File

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

View File

@@ -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<BarotraumaComponent, DamageableComponent, TransformComponent>(false))
foreach (var (barotrauma, damageable, transform) in EntityManager.EntityQuery<BarotraumaComponent, DamageableComponent, TransformComponent>())
{
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<ServerAlertsComponent>(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;
}
}

View File

@@ -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)
{

View File

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

View File

@@ -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
/// </summary>
private void UpdateBuckleStatus()
{
if (_serverAlerts == null)
{
return;
}
if (Buckled)
{
_serverAlerts.ShowAlert(BuckledTo?.BuckledAlertType ?? AlertType.Buckled);
AlertType alertType = BuckledTo?.BuckledAlertType ?? AlertType.Buckled;
EntitySystem.Get<AlertsSystem>().ShowAlert(Owner, alertType);
}
else
{
_serverAlerts.ClearAlertCategory(AlertCategory.Buckled);
EntitySystem.Get<AlertsSystem>().ClearAlertCategory(Owner, AlertCategory.Buckled);
}
}

View File

@@ -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)
{
alerts.ShowAlert(AlertType.Magboots);
_alertsSystem.ShowAlert(parent, AlertType.Magboots);
}
else
{
alerts.ClearAlert(AlertType.Magboots);
}
_alertsSystem.ClearAlert(parent, AlertType.Magboots);
}
}

View File

@@ -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;
@@ -157,17 +156,14 @@ namespace Content.Server.Cuffs.Components
/// Updates the status effect indicator on the HUD.
/// </summary>
private void UpdateAlert()
{
if (_entMan.TryGetComponent(Owner, out ServerAlertsComponent? status))
{
if (CanStillInteract)
{
status.ClearAlert(AlertType.Handcuffed);
EntitySystem.Get<AlertsSystem>().ClearAlert(Owner, AlertType.Handcuffed);
}
else
{
status.ShowAlert(AlertType.Handcuffed);
}
EntitySystem.Get<AlertsSystem>().ShowAlert(Owner, AlertType.Handcuffed);
}
}

View File

@@ -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,9 +108,11 @@ 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<Node>(id, out var node)
&& node.NodeGroup is IBasePowerNet { NetworkNode: { LastAvailableSupplySum: >0 } })
if (id != null && nodeContainer.TryGetNode<Node>(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
/// <returns>Whether the entity <see cref="uid"/> was stunned by the shock.</returns>
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<ElectrocutedComponent>(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<DamageTypePrototype>(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());

View File

@@ -89,7 +89,6 @@ namespace Content.Server.Entry
IoCManager.Resolve<ISandboxManager>().Initialize();
IoCManager.Resolve<RecipeManager>().Initialize();
IoCManager.Resolve<AlertManager>().Initialize();
IoCManager.Resolve<ActionManager>().Initialize();
IoCManager.Resolve<BlackboardManager>().Initialize();
IoCManager.Resolve<ConsiderationsManager>().Initialize();

View File

@@ -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<GridId, List<ServerAlertsComponent>> _alerts = new();
private readonly Dictionary<GridId, List<AlertsComponent>> _alerts = new();
public override void Initialize()
{
@@ -25,6 +25,7 @@ namespace Content.Server.Gravity.EntitySystems
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<GravityChangedMessage>(GravityChanged);
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
SubscribeLocalEvent<AlertsComponent, AlertSyncEvent>(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<TransformComponent>(status.Owner).GridID;
var alerts = _alerts.GetOrNew(gridId);
@@ -43,16 +44,16 @@ namespace Content.Server.Gravity.EntitySystems
{
if (EntityManager.GetComponent<GravityComponent>(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<TransformComponent>(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;
}
}
}
}

View File

@@ -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<IServerPreferencesManager, ServerPreferencesManager>();
IoCManager.Register<IServerDbManager, ServerDbManager>();
IoCManager.Register<RecipeManager, RecipeManager>();
IoCManager.Register<AlertManager, AlertManager>();
IoCManager.Register<ActionManager, ActionManager>();
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
IoCManager.Register<BlackboardManager, BlackboardManager>();

View File

@@ -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<AlertsSystem>().ShowAlert(uid, AlertType.HumanDead);
if (entityManager.TryGetComponent(uid, out StatusEffectsComponent? stun))
{

View File

@@ -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<AlertsSystem>().ShowAlert(entity, AlertType.HumanHealth, modifier);
}
}
}

View File

@@ -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<AlertsSystem>().ShowAlert(Owner, alertId);
}
else
{
alertsComponent?.ClearAlertCategory(AlertCategory.Hunger);
EntitySystem.Get<AlertsSystem>().ClearAlertCategory(Owner, AlertCategory.Hunger);
}
switch (_currentHungerThreshold)

View File

@@ -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<AlertsSystem>().ShowAlert(Owner, alertId);
}
else
{
alertsComponent?.ClearAlertCategory(AlertCategory.Thirst);
EntitySystem.Get<AlertsSystem>().ClearAlertCategory(Owner, AlertCategory.Thirst);
}
switch (_currentThirstThreshold)

View File

@@ -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"));

View File

@@ -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<StutteringAccentComponent, AccentGetEvent>(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<StutteringAccentComponent>(uid, StutterKey, time, refresh, status, alerts);
_statusEffectsSystem.TryAddStatusEffect<StutteringAccentComponent>(uid, StutterKey, time, refresh, status);
}
private void OnAccent(EntityUid uid, StutteringAccentComponent component, AccentGetEvent args)

View File

@@ -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<StatusEffectsComponent>(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);
}
}
}

View File

@@ -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!;
/// <summary>
@@ -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<TemperatureComponent, OnTemperatureChangeEvent>(EnqueueDamage);
SubscribeLocalEvent<TemperatureComponent, AtmosExposedUpdateEvent>(OnAtmosExposedUpdate);
SubscribeLocalEvent<ServerAlertsComponent, OnTemperatureChangeEvent>(ServerAlert);
SubscribeLocalEvent<AlertsComponent, OnTemperatureChangeEvent>(ServerAlert);
SubscribeLocalEvent<TemperatureProtectionComponent, ModifyChangedTemperatureEvent>(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<DamageableComponent>(uid, out var damage))
if (!EntityManager.HasComponent<DamageableComponent>(uid))
return;
// See this link for where the scaling func comes from:

View File

@@ -0,0 +1,16 @@
namespace Content.Shared.Alert;
/// <summary>
/// Every category of alert. Corresponds to category field in alert prototypes defined in YML
/// </summary>
public enum AlertCategory
{
Pressure,
Temperature,
Breathing,
Buckled,
Health,
Piloting,
Hunger,
Thirst
}

View File

@@ -0,0 +1,62 @@
using System;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
namespace Content.Shared.Alert;
/// <summary>
/// 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.
/// </summary>
[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;
}
/// <param name="category">alert category, must not be null</param>
/// <returns>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.</returns>
public static AlertKey ForCategory(AlertCategory category)
{
return new(null, category);
}
}

View File

@@ -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
{
/// <summary>
/// Provides access to all configured alerts by alert type.
/// </summary>
public class AlertManager
{
[Dependency]
private readonly IPrototypeManager _prototypeManager = default!;
private readonly Dictionary<AlertType, AlertPrototype> _typeToAlert = new();
public void Initialize()
{
foreach (var alert in _prototypeManager.EnumeratePrototypes<AlertPrototype>())
{
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);
}
}
}
/// <summary>
/// Tries to get the alert of the indicated type
/// </summary>
/// <returns>true if found</returns>
public bool TryGet(AlertType alertType, [NotNullWhen(true)] out AlertPrototype? alert)
{
return _typeToAlert.TryGetValue(alertType, out alert);
}
}
}

View File

@@ -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
}
}
}
/// <summary>
/// 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.
/// </summary>
[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;
}
/// <param name="category">alert category, must not be null</param>
/// <returns>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.</returns>
public static AlertKey ForCategory(AlertCategory category)
{
return new(null, category);
}
}
}

View File

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

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.Alert;
/// <summary>
/// Raised when the AlertSystem needs alert sources to recalculate their alert states and set them.
/// </summary>
public class AlertSyncEvent : EntityEventArgs
{
public EntityUid Euid { get; }
public AlertSyncEvent(EntityUid euid)
{
Euid = euid;
}
}

View File

@@ -1,20 +1,5 @@
namespace Content.Shared.Alert
{
/// <summary>
/// Every category of alert. Corresponds to category field in alert prototypes defined in YML
/// </summary>
public enum AlertCategory
{
Pressure,
Temperature,
Breathing,
Buckled,
Health,
Piloting,
Hunger,
Thirst
}
/// <summary>
/// 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

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Alert;
/// <summary>
/// Handles the icons on the right side of the screen.
/// Should only be used for player-controlled entities.
/// </summary>
[RegisterComponent]
[NetworkedComponent]
[ComponentProtoName("Alerts")]
public class AlertsComponent : Component
{
[ViewVariables] public Dictionary<AlertKey, AlertState> Alerts = new();
}

View File

@@ -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<AlertKey, AlertState> Alerts;
public AlertsComponentState(Dictionary<AlertKey, AlertState> alerts)
{
Alerts = alerts;
}
}

View File

@@ -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<AlertType, AlertPrototype> _typeToAlert = new();
public IReadOnlyDictionary<AlertKey, AlertState>? 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;
}
/// <returns>true iff an alert of the indicated alert category is currently showing</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="euid"></param>
/// <param name="alertType">type of the alert to set</param>
/// <param name="severity">severity, if supported by the alert</param>
/// <param name="cooldown">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)</param>
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);
}
}
/// <summary>
/// Clear the alert with the given category, if one is currently showing.
/// </summary>
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();
}
/// <summary>
/// Clear the alert of the given type if it is currently showing.
/// </summary>
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);
}
}
/// <summary>
/// Invoked after showing an alert prior to dirtying the component
/// </summary>
/// <param name="alertsComponent"></param>
protected virtual void AfterShowAlert(AlertsComponent alertsComponent) { }
/// <summary>
/// Invoked after clearing an alert prior to dirtying the component
/// </summary>
/// <param name="alertsComponent"></param>
protected virtual void AfterClearAlert(AlertsComponent alertsComponent) { }
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AlertsComponent, ComponentStartup>((uid, _, _) => RaiseLocalEvent(uid, new AlertSyncEvent(uid)));
SubscribeLocalEvent<AlertsComponent, ComponentShutdown>((uid, _, _) => HandleComponentShutdown(uid));
SubscribeLocalEvent<AlertsComponent, ComponentGetState>(ClientAlertsGetState);
SubscribeNetworkEvent<ClickAlertEvent>(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<AlertPrototype>())
{
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);
}
}
}
/// <summary>
/// Tries to get the alert of the indicated type
/// </summary>
/// <returns>true if found</returns>
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<AlertsComponent>(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<MetaDataComponent>(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);
}
}

View File

@@ -0,0 +1,19 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.Alert;
/// <summary>
/// A message that calls the click interaction on a alert
/// </summary>
[Serializable, NetSerializable]
public class ClickAlertEvent : EntityEventArgs
{
public readonly AlertType Type;
public ClickAlertEvent(AlertType alertType)
{
Type = alertType;
}
}

View File

@@ -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
/// <summary>
/// Invoked on server side when user clicks an alert.
/// </summary>
/// <param name="args"></param>
void AlertClicked(ClickAlertEventArgs args);
}
public class ClickAlertEventArgs : EventArgs
{
/// <summary>
/// Player clicking the alert
/// </summary>
public readonly EntityUid Player;
/// <summary>
/// Alert that was clicked
/// </summary>
public readonly AlertPrototype Alert;
public ClickAlertEventArgs(EntityUid player, AlertPrototype alert)
{
Player = player;
Alert = alert;
}
/// <param name="player"></param>
void AlertClicked(EntityUid player);
}
}

View File

@@ -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
{
/// <summary>
/// Handles the icons on the right side of the screen.
/// Should only be used for player-controlled entities.
/// </summary>
[NetworkedComponent()]
public abstract class SharedAlertsComponent : Component
{
[Dependency]
protected readonly AlertManager AlertManager = default!;
public override string Name => "Alerts";
[ViewVariables] private Dictionary<AlertKey, AlertState> _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);
}
/// <returns>true iff an alert of the indicated alert category is currently showing</returns>
public bool IsShowingAlertCategory(AlertCategory alertCategory)
{
return IsShowingAlert(AlertKey.ForCategory(alertCategory));
}
/// <returns>true iff an alert of the indicated id is currently showing</returns>
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;
}
/// <returns>true iff an alert of the indicated key is currently showing</returns>
protected bool IsShowingAlert(AlertKey alertKey)
{
return _alerts.ContainsKey(alertKey);
}
protected IEnumerable<KeyValuePair<AlertKey, AlertState>> EnumerateAlertStates()
{
return _alerts;
}
protected bool TryGetAlertState(AlertKey key, out AlertState alertState)
{
return _alerts.TryGetValue(key, out alertState);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="alertType">type of the alert to set</param>
/// <param name="severity">severity, if supported by the alert</param>
/// <param name="cooldown">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)</param>
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);
}
}
/// <summary>
/// Clear the alert with the given category, if one is currently showing.
/// </summary>
public void ClearAlertCategory(AlertCategory category)
{
var key = AlertKey.ForCategory(category);
if (!_alerts.Remove(key))
{
return;
}
AfterClearAlert();
Dirty();
}
/// <summary>
/// Clear the alert of the given type if it is currently showing.
/// </summary>
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);
}
}
/// <summary>
/// Invoked after showing an alert prior to dirtying the component
/// </summary>
protected virtual void AfterShowAlert() { }
/// <summary>
/// Invoked after clearing an alert prior to dirtying the component
/// </summary>
protected virtual void AfterClearAlert() { }
}
[Serializable, NetSerializable]
public class AlertsComponentState : ComponentState
{
public Dictionary<AlertKey, AlertState> Alerts;
public AlertsComponentState(Dictionary<AlertKey, AlertState> alerts)
{
Alerts = alerts;
}
}
/// <summary>
/// A message that calls the click interaction on a alert
/// </summary>
[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;
}
}

View File

@@ -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
/// <param name="frequency">Frequency for jittering. See <see cref="MaxFrequency"/> and <see cref="MinFrequency"/>.</param>
/// <param name="forceValueChange">Whether to change any existing jitter value even if they're greater than the ones we're setting.</param>
/// <param name="status">The status effects component to modify.</param>
/// <param name="alerts">The alerts component.</param>
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<JitteringComponent>(uid, "Jitter", time, refresh, status, alerts))
if (StatusEffects.TryAddStatusEffect<JitteringComponent>(uid, "Jitter", time, refresh, status))
{
var jittering = EntityManager.GetComponent<JitteringComponent>(uid);

View File

@@ -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<AlertsSystem>().ClearAlert(Owner, AlertType.HumanHealth);
base.OnRemove();
}

View File

@@ -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<AlertsSystem>().ShowAlert(uid, AlertType.HumanCrit); // TODO: combine humancrit-0 and humancrit-1 into a gif and display it
EntitySystem.Get<StandingStateSystem>().Down(uid);

View File

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

View File

@@ -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!;
/// <summary>
/// A mapping of pullers to the entity that they are pulling.
@@ -106,8 +107,7 @@ 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)

View File

@@ -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)
{
}
}

View File

@@ -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
/// <param name="time">How long the effect should last for.</param>
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if the effect could not be added or the component already exists, true otherwise.</returns>
/// <typeparam name="T">The component type to add and remove from the entity.</typeparam>
public bool TryAddStatusEffect<T>(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<T>(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,7 +136,6 @@ namespace Content.Shared.StatusEffect
/// <param name="time">How long the effect should last for.</param>
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if the effect could not be added, or if the effect already existed.</returns>
/// <remarks>
/// This obviously does not add any actual 'effects' on its own. Use the generic overload,
@@ -152,16 +145,13 @@ namespace Content.Shared.StatusEffect
/// If you want special 'effect merging' behavior, do it your own damn self!
/// </remarks>
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<StatusEffectPrototype>(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
/// <param name="uid">The entity to remove an effect from.</param>
/// <param name="key">The effect ID to remove.</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if the effect could not be removed, true otherwise.</returns>
/// <remarks>
/// 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.
/// </remarks>
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<StatusEffectPrototype>(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
/// </summary>
/// <param name="uid">The entity to remove effects from.</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if any status effects failed to be removed, true if they all did.</returns>
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
/// <param name="time">The amount of time to add.</param>
/// <param name="status">The status effect component, should you already have it.</param>
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<StatusEffectPrototype>(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
/// <param name="time">The amount of time to add.</param>
/// <param name="status">The status effect component, should you already have it.</param>
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<StatusEffectPrototype>(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;

View File

@@ -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.
/// </summary>
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<StunnedComponent>(uid, "Stun", time, refresh, alerts: alerts);
return _statusEffectSystem.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh);
}
/// <summary>
/// Knocks down the entity, making it fall to the ground.
/// </summary>
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<KnockedDownComponent>(uid, "KnockedDown", time, refresh, alerts: alerts);
return _statusEffectSystem.TryAddStatusEffect<KnockedDownComponent>(uid, "KnockedDown", time, refresh);
}
/// <summary>
/// Applies knockdown and stun to the entity temporarily.
/// </summary>
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);
}
/// <summary>
@@ -172,19 +161,15 @@ namespace Content.Shared.Stunnable
/// </summary>
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<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status, alerts))
if (_statusEffectSystem.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status))
{
var slowed = EntityManager.GetComponent<SlowedDownComponent>(uid);
// Doesn't make much sense to have the "TrySlowdown" method speed up entities now does it?

View File

@@ -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<ISerializationManager>().Initialize();
var reflection = IoCManager.Resolve<IReflectionManager>();
reflection.LoadAssemblies();
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.Initialize();
prototypeManager.LoadFromStream(new StringReader(PROTOTYPES));
var alertManager = IoCManager.Resolve<AlertManager>();
alertManager.Initialize();
Assert.That(alertManager.TryGet(AlertType.LowPressure, out var lowPressure));
Assert.That(EntitySystem.Get<AlertsSystem>().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<AlertsSystem>().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<AlertsSystem>().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<AlertsSystem>().TryGet(AlertType.HighPressure, out highPressure));
Assert.That(highPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/highpressure.png"))));
}
}

View File

@@ -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<IPrototypeManager>();
prototypeManager.Initialize();
var factory = IoCManager.Resolve<IComponentFactory>();
factory.RegisterClass<ServerAlertsComponent>();
factory.RegisterClass<AlertsComponent>();
prototypeManager.LoadFromStream(new StringReader(PROTOTYPES));
prototypeManager.Resync();
var alertManager = IoCManager.Resolve<AlertManager>();
alertManager.Initialize();
var entSys = IoCManager.Resolve<IEntitySystemManager>();
entSys.LoadExtraSystemType<ServerAlertsSystem>();
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<AlertsSystem>().TryGet(AlertType.LowPressure, out var lowpressure));
Assert.That(EntitySystem.Get<AlertsSystem>().TryGet(AlertType.HighPressure, out var highpressure));
alertsComponent.ShowAlert(AlertType.LowPressure);
EntitySystem.Get<AlertsSystem>().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<AlertsSystem>().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<AlertsSystem>().ClearAlertCategory(alertsComponent.Owner, AlertCategory.Pressure);
alertState = alertsComponent.GetComponentState() as AlertsComponentState;
Assert.That(alertState.Alerts.Count, Is.EqualTo(0));
}