ECSatize AlertsSystem (#5559)
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Content.Server/Alert/ServerAlertsSystem.cs
Normal file
7
Content.Server/Alert/ServerAlertsSystem.cs
Normal 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 { }
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
16
Content.Shared/Alert/AlertCategory.cs
Normal file
16
Content.Shared/Alert/AlertCategory.cs
Normal 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
|
||||
}
|
||||
62
Content.Shared/Alert/AlertKey.cs
Normal file
62
Content.Shared/Alert/AlertKey.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
Content.Shared/Alert/AlertState.cs
Normal file
12
Content.Shared/Alert/AlertState.cs
Normal 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;
|
||||
}
|
||||
16
Content.Shared/Alert/AlertSyncEvent.cs
Normal file
16
Content.Shared/Alert/AlertSyncEvent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
18
Content.Shared/Alert/AlertsComponent.cs
Normal file
18
Content.Shared/Alert/AlertsComponent.cs
Normal 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();
|
||||
}
|
||||
17
Content.Shared/Alert/AlertsComponentState.cs
Normal file
17
Content.Shared/Alert/AlertsComponentState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
237
Content.Shared/Alert/AlertsSystem.cs
Normal file
237
Content.Shared/Alert/AlertsSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
19
Content.Shared/Alert/ClickAlertEvent.cs
Normal file
19
Content.Shared/Alert/ClickAlertEvent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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"))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user