Alerts System and UI (#2529)
* #272 add bordered panel for effects bar * #272 avoid mouse overlapping tooltip when near edges, change tooltip colors to match mockups * #272 WIP defining status effect states as YML and sending them as encoded integers * #272 refactor to use new alert system * #272 refactor to use new alert system * #272 fix various bugs with new alert system and update alerts to have color * #272 WIP * #272 rename status effects to alerts * #272 WIP reworking alert internals to avoid code dup and eliminate enum * #272 refactor alerts to use categories and fix various bugs * #272 more alert bugfixes * #272 alert ordering * #272 callback-based approach for alert clicks * #272 add debug commands for alerts * #272 utilize new GridContainer capabilities for sizing of alerts tab * #272 scale alerts height based on window size * #272 fix tooltip flicker * #272 transparent alert panel * #272 adjust styles to match injazz mockups more, add cooldown info in tooltip * #272 adjust styles to match injazz mockups more, add cooldown info in tooltip * #272 alert prototype tests * #272 alert manager tests * #272 alert order tests * #272 simple unit test for alerts component * #272 integration test for alerts * #272 rework alerts to use enums instead of id / category * #272 various cleanups for PR * #272 use byte for more compact alert messages * #272 rename StatusEffects folder to Alerts, add missing NetSerializable
@@ -11,6 +11,7 @@ using Content.Client.UserInterface.AdminMenu;
|
|||||||
using Content.Client.UserInterface.Stylesheets;
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
using Content.Client.Utility;
|
using Content.Client.Utility;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
namespace Content.Client
|
namespace Content.Client
|
||||||
@@ -35,6 +36,7 @@ namespace Content.Client
|
|||||||
IoCManager.Register<IClickMapManager, ClickMapManager>();
|
IoCManager.Register<IClickMapManager, ClickMapManager>();
|
||||||
IoCManager.Register<IStationEventManager, StationEventManager>();
|
IoCManager.Register<IStationEventManager, StationEventManager>();
|
||||||
IoCManager.Register<IAdminMenuManager, AdminMenuManager>();
|
IoCManager.Register<IAdminMenuManager, AdminMenuManager>();
|
||||||
|
IoCManager.Register<AlertManager, AlertManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using Content.Shared.GameObjects.Components.Power.AME;
|
|||||||
using Content.Shared.GameObjects.Components.Research;
|
using Content.Shared.GameObjects.Components.Research;
|
||||||
using Content.Shared.GameObjects.Components.VendingMachines;
|
using Content.Shared.GameObjects.Components.VendingMachines;
|
||||||
using Content.Shared.Kitchen;
|
using Content.Shared.Kitchen;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Robust.Client;
|
using Robust.Client;
|
||||||
using Robust.Client.Interfaces;
|
using Robust.Client.Interfaces;
|
||||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||||
@@ -150,6 +151,7 @@ namespace Content.Client
|
|||||||
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
|
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
|
||||||
IoCManager.Resolve<IStationEventManager>().Initialize();
|
IoCManager.Resolve<IStationEventManager>().Initialize();
|
||||||
IoCManager.Resolve<IAdminMenuManager>().Initialize();
|
IoCManager.Resolve<IAdminMenuManager>().Initialize();
|
||||||
|
IoCManager.Resolve<AlertManager>().Initialize();
|
||||||
|
|
||||||
_baseClient.RunLevelChanged += (sender, args) =>
|
_baseClient.RunLevelChanged += (sender, args) =>
|
||||||
{
|
{
|
||||||
|
|||||||
93
Content.Client/GameObjects/Components/Mobs/AlertControl.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Client.Utility;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using OpenToolkit.Mathematics;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
public class AlertControl : BaseButton
|
||||||
|
{
|
||||||
|
public AlertPrototype Alert { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total duration of the cooldown in seconds. Null if no duration / cooldown.
|
||||||
|
/// </summary>
|
||||||
|
public int? TotalDuration { get; set; }
|
||||||
|
|
||||||
|
private short? _severity;
|
||||||
|
private readonly TextureRect _icon;
|
||||||
|
private CooldownGraphic _cooldownGraphic;
|
||||||
|
|
||||||
|
private readonly IResourceCache _resourceCache;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an alert control reflecting the indicated alert + state
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="alert">alert to display</param>
|
||||||
|
/// <param name="severity">severity of alert, null if alert doesn't have severity levels</param>
|
||||||
|
/// <param name="resourceCache">resourceCache to use to load alert icon textures</param>
|
||||||
|
public AlertControl(AlertPrototype alert, short? severity, IResourceCache resourceCache)
|
||||||
|
{
|
||||||
|
_resourceCache = resourceCache;
|
||||||
|
Alert = alert;
|
||||||
|
_severity = severity;
|
||||||
|
var texture = _resourceCache.GetTexture(alert.GetIconPath(_severity));
|
||||||
|
_icon = new TextureRect
|
||||||
|
{
|
||||||
|
TextureScale = (2, 2),
|
||||||
|
Texture = texture
|
||||||
|
};
|
||||||
|
|
||||||
|
Children.Add(_icon);
|
||||||
|
_cooldownGraphic = new CooldownGraphic();
|
||||||
|
Children.Add(_cooldownGraphic);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change the alert severity, changing the displayed icon
|
||||||
|
/// </summary>
|
||||||
|
public void SetSeverity(short? severity)
|
||||||
|
{
|
||||||
|
if (_severity != severity)
|
||||||
|
{
|
||||||
|
_severity = severity;
|
||||||
|
_icon.Texture = _resourceCache.GetTexture(Alert.GetIconPath(_severity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the displayed cooldown amount, doing nothing if alertCooldown is null
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="alertCooldown">cooldown start and end</param>
|
||||||
|
/// <param name="curTime">current game time</param>
|
||||||
|
public void UpdateCooldown((TimeSpan Start, TimeSpan End)? alertCooldown, in TimeSpan curTime)
|
||||||
|
{
|
||||||
|
if (!alertCooldown.HasValue)
|
||||||
|
{
|
||||||
|
_cooldownGraphic.Progress = 0;
|
||||||
|
_cooldownGraphic.Visible = false;
|
||||||
|
TotalDuration = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
var start = alertCooldown.Value.Start;
|
||||||
|
var end = alertCooldown.Value.End;
|
||||||
|
|
||||||
|
var length = (end - start).TotalSeconds;
|
||||||
|
var progress = (curTime - start).TotalSeconds / length;
|
||||||
|
var ratio = (progress <= 1 ? (1 - progress) : (curTime - end).TotalSeconds * -5);
|
||||||
|
|
||||||
|
TotalDuration = (int?) Math.Round(length);
|
||||||
|
_cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1);
|
||||||
|
_cooldownGraphic.Visible = ratio > -1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
|
using Content.Client.Utility;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.Graphics;
|
||||||
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Input;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedAlertsComponent))]
|
||||||
|
public sealed class ClientAlertsComponent : SharedAlertsComponent
|
||||||
|
{
|
||||||
|
private static readonly float TooltipTextMaxWidth = 265;
|
||||||
|
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
|
private AlertsUI _ui;
|
||||||
|
private PanelContainer _tooltip;
|
||||||
|
private RichTextLabel _stateName;
|
||||||
|
private RichTextLabel _stateDescription;
|
||||||
|
private RichTextLabel _stateCooldown;
|
||||||
|
private AlertOrderPrototype _alertOrder;
|
||||||
|
private bool _tooltipReady;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private Dictionary<AlertKey, AlertControl> _alertControls
|
||||||
|
= new Dictionary<AlertKey, AlertControl>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows calculating if we need to act due to this component being controlled by the current mob
|
||||||
|
/// TODO: should be revisited after space-wizards/RobustToolbox#1255
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private bool CurrentlyControlled => _playerManager.LocalPlayer != null && _playerManager.LocalPlayer.ControlledEntity == Owner;
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
PlayerDetached();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, component);
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case PlayerAttachedMsg _:
|
||||||
|
PlayerAttached();
|
||||||
|
break;
|
||||||
|
case PlayerDetachedMsg _:
|
||||||
|
PlayerDetached();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
base.HandleComponentState(curState, nextState);
|
||||||
|
|
||||||
|
if (!(curState is AlertsComponentState state))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the dict of states based on the array we got in the message
|
||||||
|
SetAlerts(state.Alerts);
|
||||||
|
|
||||||
|
UpdateAlertsControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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<IClyde>());
|
||||||
|
var uiManager = IoCManager.Resolve<IUserInterfaceManager>();
|
||||||
|
uiManager.StateRoot.AddChild(_ui);
|
||||||
|
|
||||||
|
_tooltip = new PanelContainer
|
||||||
|
{
|
||||||
|
Visible = false,
|
||||||
|
StyleClasses = { StyleNano.StyleClassTooltipPanel }
|
||||||
|
};
|
||||||
|
var tooltipVBox = new VBoxContainer
|
||||||
|
{
|
||||||
|
RectClipContent = true
|
||||||
|
};
|
||||||
|
_tooltip.AddChild(tooltipVBox);
|
||||||
|
_stateName = new RichTextLabel
|
||||||
|
{
|
||||||
|
MaxWidth = TooltipTextMaxWidth,
|
||||||
|
StyleClasses = { StyleNano.StyleClassTooltipAlertTitle }
|
||||||
|
};
|
||||||
|
tooltipVBox.AddChild(_stateName);
|
||||||
|
_stateDescription = new RichTextLabel
|
||||||
|
{
|
||||||
|
MaxWidth = TooltipTextMaxWidth,
|
||||||
|
StyleClasses = { StyleNano.StyleClassTooltipAlertDescription }
|
||||||
|
};
|
||||||
|
tooltipVBox.AddChild(_stateDescription);
|
||||||
|
_stateCooldown = new RichTextLabel
|
||||||
|
{
|
||||||
|
MaxWidth = TooltipTextMaxWidth,
|
||||||
|
StyleClasses = { StyleNano.StyleClassTooltipAlertCooldown }
|
||||||
|
};
|
||||||
|
tooltipVBox.AddChild(_stateCooldown);
|
||||||
|
|
||||||
|
uiManager.PopupRoot.AddChild(_tooltip);
|
||||||
|
|
||||||
|
UpdateAlertsControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerDetached()
|
||||||
|
{
|
||||||
|
_ui?.Dispose();
|
||||||
|
_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)
|
||||||
|
{
|
||||||
|
// remove and dispose the control
|
||||||
|
_alertControls.Remove(alertKeyToRemove, out var control);
|
||||||
|
control?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 alertStatus in EnumerateAlertStates())
|
||||||
|
{
|
||||||
|
if (!AlertManager.TryDecode(alertStatus.AlertEncoded, out var newAlert))
|
||||||
|
{
|
||||||
|
Logger.ErrorS("alert", "Unable to decode alert {0}", alertStatus.AlertEncoded);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) &&
|
||||||
|
existingAlertControl.Alert.AlertType == newAlert.AlertType)
|
||||||
|
{
|
||||||
|
// id is the same, simply update the existing control severity
|
||||||
|
existingAlertControl.SetSeverity(alertStatus.Severity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existingAlertControl?.Dispose();
|
||||||
|
|
||||||
|
// 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, alertStatus);
|
||||||
|
if (_alertOrder != null)
|
||||||
|
{
|
||||||
|
var added = false;
|
||||||
|
foreach (var alertControl in _ui.Grid.Children)
|
||||||
|
{
|
||||||
|
if (_alertOrder.Compare(newAlert, ((AlertControl) alertControl).Alert) < 0)
|
||||||
|
{
|
||||||
|
var idx = alertControl.GetPositionInParent();
|
||||||
|
_ui.Grid.Children.Add(newAlertControl);
|
||||||
|
newAlertControl.SetPositionInParent(idx);
|
||||||
|
added = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!added)
|
||||||
|
{
|
||||||
|
_ui.Grid.Children.Add(newAlertControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ui.Grid.Children.Add(newAlertControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
_alertControls[newAlert.AlertKey] = newAlertControl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState)
|
||||||
|
{
|
||||||
|
|
||||||
|
var alertControl = new AlertControl(alert, alertState.Severity, _resourceCache);
|
||||||
|
// show custom tooltip for the status control
|
||||||
|
alertControl.OnShowTooltip += AlertOnOnShowTooltip;
|
||||||
|
alertControl.OnHideTooltip += AlertOnOnHideTooltip;
|
||||||
|
|
||||||
|
alertControl.OnPressed += AlertControlOnPressed;
|
||||||
|
|
||||||
|
return alertControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AlertControlOnPressed(BaseButton.ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
AlertPressed(args, args.Button as AlertControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AlertOnOnHideTooltip(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_tooltipReady = false;
|
||||||
|
_tooltip.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AlertOnOnShowTooltip(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var alertControl = (AlertControl) sender;
|
||||||
|
_stateName.SetMessage(alertControl.Alert.Name);
|
||||||
|
_stateDescription.SetMessage(alertControl.Alert.Description);
|
||||||
|
// check for a cooldown
|
||||||
|
if (alertControl.TotalDuration != null && alertControl.TotalDuration > 0)
|
||||||
|
{
|
||||||
|
_stateCooldown.SetMessage(FormattedMessage.FromMarkup("[color=#776a6a]" +
|
||||||
|
alertControl.TotalDuration +
|
||||||
|
" sec cooldown[/color]"));
|
||||||
|
_stateCooldown.Visible = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stateCooldown.Visible = false;
|
||||||
|
}
|
||||||
|
// TODO: Text display of cooldown
|
||||||
|
Tooltips.PositionTooltip(_tooltip);
|
||||||
|
// if we set it visible here the size of the previous tooltip will flicker for a frame,
|
||||||
|
// so instead we wait until FrameUpdate to make it visible
|
||||||
|
_tooltipReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AlertPressed(BaseButton.ButtonEventArgs args, AlertControl alert)
|
||||||
|
{
|
||||||
|
if (args.Event.Function != EngineKeyFunctions.UIClick)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AlertManager.TryEncode(alert.Alert, out var encoded))
|
||||||
|
{
|
||||||
|
SendNetworkMessage(new ClickAlertMessage(encoded));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.ErrorS("alert", "unable to encode alert {0}", alert.Alert.AlertType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FrameUpdate(float frameTime)
|
||||||
|
{
|
||||||
|
if (_tooltipReady)
|
||||||
|
{
|
||||||
|
_tooltipReady = false;
|
||||||
|
_tooltip.Visible = true;
|
||||||
|
}
|
||||||
|
foreach (var (alertKey, alertControl) in _alertControls)
|
||||||
|
{
|
||||||
|
// reconcile all alert controls with their current cooldowns
|
||||||
|
if (TryGetAlertState(alertKey, out var alertState))
|
||||||
|
{
|
||||||
|
alertControl.UpdateCooldown(alertState.Cooldown, _gameTiming.CurTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.WarningS("alert", "coding error - no alert state for alert {0} " +
|
||||||
|
"even though we had an AlertControl for it, this" +
|
||||||
|
" should never happen", alertControl.Alert.AlertType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AfterClearAlert()
|
||||||
|
{
|
||||||
|
UpdateAlertsControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
base.OnRemove();
|
||||||
|
|
||||||
|
foreach (var alertControl in _alertControls.Values)
|
||||||
|
{
|
||||||
|
alertControl.OnShowTooltip -= AlertOnOnShowTooltip;
|
||||||
|
alertControl.OnHideTooltip -= AlertOnOnHideTooltip;
|
||||||
|
alertControl.OnPressed -= AlertControlOnPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Client.UserInterface;
|
|
||||||
using Content.Client.Utility;
|
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.Interfaces.ResourceManagement;
|
|
||||||
using Robust.Client.Interfaces.UserInterface;
|
|
||||||
using Robust.Client.Player;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Input;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.Timing;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Mobs
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[RegisterComponent]
|
|
||||||
[ComponentReference(typeof(SharedStatusEffectsComponent))]
|
|
||||||
public sealed class ClientStatusEffectsComponent : SharedStatusEffectsComponent
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
|
||||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
|
||||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
|
||||||
|
|
||||||
private StatusEffectsUI _ui;
|
|
||||||
[ViewVariables]
|
|
||||||
private Dictionary<StatusEffect, StatusEffectStatus> _status = new Dictionary<StatusEffect, StatusEffectStatus>();
|
|
||||||
[ViewVariables]
|
|
||||||
private Dictionary<StatusEffect, CooldownGraphic> _cooldown = new Dictionary<StatusEffect, CooldownGraphic>();
|
|
||||||
|
|
||||||
public override IReadOnlyDictionary<StatusEffect, StatusEffectStatus> Statuses => _status;
|
|
||||||
|
|
||||||
/// <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 HandleMessage(ComponentMessage message, IComponent component)
|
|
||||||
{
|
|
||||||
base.HandleMessage(message, component);
|
|
||||||
switch (message)
|
|
||||||
{
|
|
||||||
case PlayerAttachedMsg _:
|
|
||||||
PlayerAttached();
|
|
||||||
break;
|
|
||||||
case PlayerDetachedMsg _:
|
|
||||||
PlayerDetached();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
|
||||||
{
|
|
||||||
base.HandleComponentState(curState, nextState);
|
|
||||||
|
|
||||||
if (!(curState is StatusEffectComponentState state) || _status == state.StatusEffects)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_status = state.StatusEffects;
|
|
||||||
UpdateStatusEffects();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayerAttached()
|
|
||||||
{
|
|
||||||
if (!CurrentlyControlled || _ui != null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ui = new StatusEffectsUI();
|
|
||||||
_userInterfaceManager.StateRoot.AddChild(_ui);
|
|
||||||
UpdateStatusEffects();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayerDetached()
|
|
||||||
{
|
|
||||||
_ui?.Dispose();
|
|
||||||
_ui = null;
|
|
||||||
_cooldown.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ChangeStatusEffectIcon(StatusEffect effect, string icon)
|
|
||||||
{
|
|
||||||
if (_status.TryGetValue(effect, out var value) &&
|
|
||||||
value.Icon == icon)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_status[effect] = new StatusEffectStatus
|
|
||||||
{
|
|
||||||
Icon = icon,
|
|
||||||
Cooldown = value.Cooldown
|
|
||||||
};
|
|
||||||
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateStatusEffects()
|
|
||||||
{
|
|
||||||
if (!CurrentlyControlled || _ui == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_cooldown.Clear();
|
|
||||||
_ui.VBox.DisposeAllChildren();
|
|
||||||
|
|
||||||
foreach (var (key, effect) in _status.OrderBy(x => (int) x.Key))
|
|
||||||
{
|
|
||||||
var texture = _resourceCache.GetTexture(effect.Icon);
|
|
||||||
var status = new StatusControl(key, texture)
|
|
||||||
{
|
|
||||||
ToolTip = key.ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (effect.Cooldown.HasValue)
|
|
||||||
{
|
|
||||||
var cooldown = new CooldownGraphic();
|
|
||||||
status.Children.Add(cooldown);
|
|
||||||
_cooldown[key] = cooldown;
|
|
||||||
}
|
|
||||||
|
|
||||||
status.OnPressed += args => StatusPressed(args, status);
|
|
||||||
|
|
||||||
_ui.VBox.AddChild(status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StatusPressed(BaseButton.ButtonEventArgs args, StatusControl status)
|
|
||||||
{
|
|
||||||
if (args.Event.Function != EngineKeyFunctions.UIClick)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SendNetworkMessage(new ClickStatusMessage(status.Effect));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void RemoveStatusEffect(StatusEffect effect)
|
|
||||||
{
|
|
||||||
if (!_status.Remove(effect))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateStatusEffects();
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FrameUpdate(float frameTime)
|
|
||||||
{
|
|
||||||
foreach (var (effect, cooldownGraphic) in _cooldown)
|
|
||||||
{
|
|
||||||
var status = _status[effect];
|
|
||||||
if (!status.Cooldown.HasValue)
|
|
||||||
{
|
|
||||||
cooldownGraphic.Progress = 0;
|
|
||||||
cooldownGraphic.Visible = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var start = status.Cooldown.Value.Item1;
|
|
||||||
var end = status.Cooldown.Value.Item2;
|
|
||||||
|
|
||||||
var length = (end - start).TotalSeconds;
|
|
||||||
var progress = (_gameTiming.CurTime - start).TotalSeconds / length;
|
|
||||||
var ratio = (progress <= 1 ? (1 - progress) : (_gameTiming.CurTime - end).TotalSeconds * -5);
|
|
||||||
|
|
||||||
cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1);
|
|
||||||
cooldownGraphic.Visible = ratio > -1f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ChangeStatusEffect(StatusEffect effect, string icon, (TimeSpan, TimeSpan)? cooldown)
|
|
||||||
{
|
|
||||||
_status[effect] = new StatusEffectStatus()
|
|
||||||
{
|
|
||||||
Icon = icon,
|
|
||||||
Cooldown = cooldown
|
|
||||||
};
|
|
||||||
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Mobs
|
|
||||||
{
|
|
||||||
public class StatusControl : BaseButton
|
|
||||||
{
|
|
||||||
public readonly StatusEffect Effect;
|
|
||||||
|
|
||||||
public StatusControl(StatusEffect effect, Texture? texture)
|
|
||||||
{
|
|
||||||
Effect = effect;
|
|
||||||
|
|
||||||
var item = new TextureRect
|
|
||||||
{
|
|
||||||
TextureScale = (2, 2),
|
|
||||||
Texture = texture
|
|
||||||
};
|
|
||||||
|
|
||||||
Children.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
using Content.Shared.AI;
|
using Content.Shared.AI;
|
||||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
using Robust.Client.Interfaces.UserInterface;
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
@@ -178,7 +179,7 @@ namespace Content.Client.GameObjects.EntitySystems.AI
|
|||||||
|
|
||||||
var panel = new PanelContainer
|
var panel = new PanelContainer
|
||||||
{
|
{
|
||||||
StyleClasses = {"tooltipBox"},
|
StyleClasses = { StyleNano.StyleClassTooltipPanel },
|
||||||
Children = {vBox},
|
Children = {vBox},
|
||||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||||
ModulateSelfOverride = Color.White.WithAlpha(0.75f),
|
ModulateSelfOverride = Color.White.WithAlpha(0.75f),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using Robust.Shared.IoC;
|
|||||||
|
|
||||||
namespace Content.Client.GameObjects.EntitySystems
|
namespace Content.Client.GameObjects.EntitySystems
|
||||||
{
|
{
|
||||||
public class StatusEffectsSystem : EntitySystem
|
public class AlertsSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
@@ -16,9 +16,9 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
if (!_gameTiming.IsFirstTimePredicted)
|
if (!_gameTiming.IsFirstTimePredicted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var clientStatusEffectsComponent in EntityManager.ComponentManager.EntityQuery<ClientStatusEffectsComponent>())
|
foreach (var clientAlertsComponent in EntityManager.ComponentManager.EntityQuery<ClientAlertsComponent>())
|
||||||
{
|
{
|
||||||
clientStatusEffectsComponent.FrameUpdate(frameTime);
|
clientAlertsComponent.FrameUpdate(frameTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
73
Content.Client/UserInterface/AlertsUI.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
|
using Robust.Client.Graphics.Drawing;
|
||||||
|
using Robust.Client.Interfaces.Graphics;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The status effects display on the right side of the screen.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AlertsUI : Control
|
||||||
|
{
|
||||||
|
public GridContainer Grid { get; }
|
||||||
|
|
||||||
|
private readonly IClyde _clyde;
|
||||||
|
|
||||||
|
public AlertsUI(IClyde clyde)
|
||||||
|
{
|
||||||
|
_clyde = clyde;
|
||||||
|
var panelContainer = new PanelContainer
|
||||||
|
{
|
||||||
|
StyleClasses = {StyleNano.StyleClassTransparentBorderedWindowPanel},
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
};
|
||||||
|
AddChild(panelContainer);
|
||||||
|
|
||||||
|
Grid = new GridContainer
|
||||||
|
{
|
||||||
|
MaxHeight = CalcMaxHeight(clyde.ScreenSize),
|
||||||
|
ExpandBackwards = true
|
||||||
|
};
|
||||||
|
panelContainer.AddChild(Grid);
|
||||||
|
clyde.OnWindowResized += ClydeOnOnWindowResized;
|
||||||
|
|
||||||
|
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
|
||||||
|
LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.TopRight, margin: 10);
|
||||||
|
LayoutContainer.SetMarginTop(this, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UIScaleChanged()
|
||||||
|
{
|
||||||
|
Grid.MaxHeight = CalcMaxHeight(_clyde.ScreenSize);
|
||||||
|
base.UIScaleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClydeOnOnWindowResized(WindowResizedEventArgs obj)
|
||||||
|
{
|
||||||
|
// TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done,
|
||||||
|
// this is here because there isn't currently a good way to allow the grid to adjust its height based
|
||||||
|
// on constraints, otherwise we would use anchors to lay it out
|
||||||
|
Grid.MaxHeight = CalcMaxHeight(obj.NewSize);;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float CalcMaxHeight(Vector2i screenSize)
|
||||||
|
{
|
||||||
|
return Math.Max(((screenSize.Y) / UIScale) - 420, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_clyde.OnWindowResized -= ClydeOnOnWindowResized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
|
|
||||||
namespace Content.Client.UserInterface
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The status effects display on the right side of the screen.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class StatusEffectsUI : Control
|
|
||||||
{
|
|
||||||
public VBoxContainer VBox { get; }
|
|
||||||
|
|
||||||
public StatusEffectsUI()
|
|
||||||
{
|
|
||||||
VBox = new VBoxContainer();
|
|
||||||
AddChild(VBox);
|
|
||||||
|
|
||||||
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
|
|
||||||
LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.TopRight, margin: 10);
|
|
||||||
LayoutContainer.SetMarginTop(this, 250);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,13 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
{
|
{
|
||||||
public sealed class StyleNano : StyleBase
|
public sealed class StyleNano : StyleBase
|
||||||
{
|
{
|
||||||
|
public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel";
|
||||||
|
public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel";
|
||||||
|
public const string StyleClassTooltipPanel = "tooltipBox";
|
||||||
|
public const string StyleClassTooltipAlertTitle = "tooltipAlertTitle";
|
||||||
|
public const string StyleClassTooltipAlertDescription = "tooltipAlertDesc";
|
||||||
|
public const string StyleClassTooltipAlertCooldown = "tooltipAlertCooldown";
|
||||||
|
|
||||||
public const string StyleClassSliderRed = "Red";
|
public const string StyleClassSliderRed = "Red";
|
||||||
public const string StyleClassSliderGreen = "Green";
|
public const string StyleClassSliderGreen = "Green";
|
||||||
public const string StyleClassSliderBlue = "Blue";
|
public const string StyleClassSliderBlue = "Blue";
|
||||||
@@ -55,6 +62,7 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
var notoSansDisplayBold14 = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 14);
|
var notoSansDisplayBold14 = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 14);
|
||||||
var notoSans16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 16);
|
var notoSans16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 16);
|
||||||
var notoSansBold16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 16);
|
var notoSansBold16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 16);
|
||||||
|
var notoSansBold18 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 18);
|
||||||
var notoSansBold20 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 20);
|
var notoSansBold20 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 20);
|
||||||
var textureCloseButton = resCache.GetTexture("/Textures/Interface/Nano/cross.svg.png");
|
var textureCloseButton = resCache.GetTexture("/Textures/Interface/Nano/cross.svg.png");
|
||||||
var windowHeaderTex = resCache.GetTexture("/Textures/Interface/Nano/window_header.png");
|
var windowHeaderTex = resCache.GetTexture("/Textures/Interface/Nano/window_header.png");
|
||||||
@@ -73,6 +81,20 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
windowBackground.SetPatchMargin(StyleBox.Margin.Horizontal | StyleBox.Margin.Bottom, 2);
|
windowBackground.SetPatchMargin(StyleBox.Margin.Horizontal | StyleBox.Margin.Bottom, 2);
|
||||||
windowBackground.SetExpandMargin(StyleBox.Margin.Horizontal | StyleBox.Margin.Bottom, 2);
|
windowBackground.SetExpandMargin(StyleBox.Margin.Horizontal | StyleBox.Margin.Bottom, 2);
|
||||||
|
|
||||||
|
var borderedWindowBackgroundTex = resCache.GetTexture("/Textures/Interface/Nano/window_background_bordered.png");
|
||||||
|
var borderedWindowBackground = new StyleBoxTexture
|
||||||
|
{
|
||||||
|
Texture = borderedWindowBackgroundTex,
|
||||||
|
};
|
||||||
|
borderedWindowBackground.SetPatchMargin(StyleBox.Margin.All, 2);
|
||||||
|
|
||||||
|
var borderedTransparentWindowBackgroundTex = resCache.GetTexture("/Textures/Interface/Nano/transparent_window_background_bordered.png");
|
||||||
|
var borderedTransparentWindowBackground = new StyleBoxTexture
|
||||||
|
{
|
||||||
|
Texture = borderedTransparentWindowBackgroundTex,
|
||||||
|
};
|
||||||
|
borderedTransparentWindowBackground.SetPatchMargin(StyleBox.Margin.All, 2);
|
||||||
|
|
||||||
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
|
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
|
||||||
|
|
||||||
var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png");
|
var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png");
|
||||||
@@ -147,7 +169,7 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
Texture = tooltipTexture,
|
Texture = tooltipTexture,
|
||||||
};
|
};
|
||||||
tooltipBox.SetPatchMargin(StyleBox.Margin.All, 2);
|
tooltipBox.SetPatchMargin(StyleBox.Margin.All, 2);
|
||||||
tooltipBox.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
|
tooltipBox.SetContentMarginOverride(StyleBox.Margin.Horizontal, 7);
|
||||||
|
|
||||||
// Placeholder
|
// Placeholder
|
||||||
var placeholderTexture = resCache.GetTexture("/Textures/Interface/Nano/placeholder.png");
|
var placeholderTexture = resCache.GetTexture("/Textures/Interface/Nano/placeholder.png");
|
||||||
@@ -245,6 +267,19 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
{
|
{
|
||||||
new StyleProperty(PanelContainer.StylePropertyPanel, windowBackground),
|
new StyleProperty(PanelContainer.StylePropertyPanel, windowBackground),
|
||||||
}),
|
}),
|
||||||
|
// bordered window background
|
||||||
|
new StyleRule(
|
||||||
|
new SelectorElement(null, new[] {StyleClassBorderedWindowPanel}, null, null),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new StyleProperty(PanelContainer.StylePropertyPanel, borderedWindowBackground),
|
||||||
|
}),
|
||||||
|
new StyleRule(
|
||||||
|
new SelectorElement(null, new[] {StyleClassTransparentBorderedWindowPanel}, null, null),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new StyleProperty(PanelContainer.StylePropertyPanel, borderedTransparentWindowBackground),
|
||||||
|
}),
|
||||||
// Window header.
|
// Window header.
|
||||||
new StyleRule(
|
new StyleRule(
|
||||||
new SelectorElement(typeof(PanelContainer), new[] {SS14Window.StyleClassWindowHeader}, null, null),
|
new SelectorElement(typeof(PanelContainer), new[] {SS14Window.StyleClassWindowHeader}, null, null),
|
||||||
@@ -464,7 +499,7 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
|
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {"tooltipBox"}, null, null), new[]
|
new StyleRule(new SelectorElement(typeof(PanelContainer), new [] { StyleClassTooltipPanel }, null, null), new[]
|
||||||
{
|
{
|
||||||
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
|
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
|
||||||
}),
|
}),
|
||||||
@@ -482,6 +517,20 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
new StyleProperty("font", notoSansItalic12),
|
new StyleProperty("font", notoSansItalic12),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// alert tooltip
|
||||||
|
new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipAlertTitle}, null, null), new[]
|
||||||
|
{
|
||||||
|
new StyleProperty("font", notoSansBold18)
|
||||||
|
}),
|
||||||
|
new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipAlertDescription}, null, null), new[]
|
||||||
|
{
|
||||||
|
new StyleProperty("font", notoSans16)
|
||||||
|
}),
|
||||||
|
new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipAlertCooldown}, null, null), new[]
|
||||||
|
{
|
||||||
|
new StyleProperty("font", notoSans16)
|
||||||
|
}),
|
||||||
|
|
||||||
// Entity tooltip
|
// Entity tooltip
|
||||||
new StyleRule(
|
new StyleRule(
|
||||||
new SelectorElement(typeof(PanelContainer), new[] {ExamineSystem.StyleClassEntityTooltip}, null,
|
new SelectorElement(typeof(PanelContainer), new[] {ExamineSystem.StyleClassEntityTooltip}, null,
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Client.GameObjects.Components.Mobs;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[TestOf(typeof(ClientAlertsComponent))]
|
||||||
|
[TestOf(typeof(ServerAlertsComponent))]
|
||||||
|
public class AlertsComponentTests : ContentIntegrationTest
|
||||||
|
{
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task AlertsTest()
|
||||||
|
{
|
||||||
|
var (client, server) = await StartConnectedServerClientPair();
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
await client.WaitIdleAsync();
|
||||||
|
|
||||||
|
var serverPlayerManager = server.ResolveDependency<Robust.Server.Interfaces.Player.IPlayerManager>();
|
||||||
|
|
||||||
|
await server.WaitAssertion(() =>
|
||||||
|
{
|
||||||
|
var player = serverPlayerManager.GetAllPlayers().Single();
|
||||||
|
var playerEnt = player.AttachedEntity;
|
||||||
|
Assert.NotNull(playerEnt);
|
||||||
|
var alertsComponent = playerEnt.GetComponent<ServerAlertsComponent>();
|
||||||
|
Assert.NotNull(alertsComponent);
|
||||||
|
|
||||||
|
// show 2 alerts
|
||||||
|
alertsComponent.ShowAlert(AlertType.Debug1);
|
||||||
|
alertsComponent.ShowAlert(AlertType.Debug2);
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitRunTicks(5);
|
||||||
|
await client.WaitRunTicks(5);
|
||||||
|
|
||||||
|
var clientPlayerMgr = client.ResolveDependency<IPlayerManager>();
|
||||||
|
var clientUIMgr = client.ResolveDependency<IUserInterfaceManager>();
|
||||||
|
await client.WaitAssertion(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
var local = clientPlayerMgr.LocalPlayer;
|
||||||
|
Assert.NotNull(local);
|
||||||
|
var controlled = local.ControlledEntity;
|
||||||
|
Assert.NotNull(controlled);
|
||||||
|
var alertsComponent = controlled.GetComponent<ClientAlertsComponent>();
|
||||||
|
Assert.NotNull(alertsComponent);
|
||||||
|
|
||||||
|
// find the alertsui
|
||||||
|
var alertsUI =
|
||||||
|
clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is AlertsUI) as AlertsUI;
|
||||||
|
Assert.NotNull(alertsUI);
|
||||||
|
|
||||||
|
// we should be seeing 3 alerts - our health, and the 2 debug alerts, in a specific order.
|
||||||
|
Assert.That(alertsUI.Grid.ChildCount, Is.EqualTo(3));
|
||||||
|
var alertControls = alertsUI.Grid.Children.Select(c => c as AlertControl);
|
||||||
|
var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray();
|
||||||
|
var expectedIDs = new [] {AlertType.HumanHealth, AlertType.Debug1, AlertType.Debug2};
|
||||||
|
Assert.That(alertIDs, Is.EqualTo(expectedIDs));
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitAssertion(() =>
|
||||||
|
{
|
||||||
|
var player = serverPlayerManager.GetAllPlayers().Single();
|
||||||
|
var playerEnt = player.AttachedEntity;
|
||||||
|
Assert.NotNull(playerEnt);
|
||||||
|
var alertsComponent = playerEnt.GetComponent<ServerAlertsComponent>();
|
||||||
|
Assert.NotNull(alertsComponent);
|
||||||
|
|
||||||
|
alertsComponent.ClearAlert(AlertType.Debug1);
|
||||||
|
});
|
||||||
|
await server.WaitRunTicks(5);
|
||||||
|
await client.WaitRunTicks(5);
|
||||||
|
|
||||||
|
await client.WaitAssertion(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
var local = clientPlayerMgr.LocalPlayer;
|
||||||
|
Assert.NotNull(local);
|
||||||
|
var controlled = local.ControlledEntity;
|
||||||
|
Assert.NotNull(controlled);
|
||||||
|
var alertsComponent = controlled.GetComponent<ClientAlertsComponent>();
|
||||||
|
Assert.NotNull(alertsComponent);
|
||||||
|
|
||||||
|
// find the alertsui
|
||||||
|
var alertsUI =
|
||||||
|
clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is AlertsUI) as AlertsUI;
|
||||||
|
Assert.NotNull(alertsUI);
|
||||||
|
|
||||||
|
// we should be seeing only 2 alerts now because one was cleared
|
||||||
|
Assert.That(alertsUI.Grid.ChildCount, Is.EqualTo(2));
|
||||||
|
var alertControls = alertsUI.Grid.Children.Select(c => c as AlertControl);
|
||||||
|
var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray();
|
||||||
|
var expectedIDs = new [] {AlertType.HumanHealth, AlertType.Debug2};
|
||||||
|
Assert.That(alertIDs, Is.EqualTo(expectedIDs));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameObjects.Components.Gravity;
|
using Content.Server.GameObjects.Components.Gravity;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Gravity;
|
using Content.Shared.GameObjects.Components.Gravity;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
@@ -32,7 +33,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
|||||||
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||||
|
|
||||||
IEntity human = null;
|
IEntity human = null;
|
||||||
SharedStatusEffectsComponent statusEffects = null;
|
SharedAlertsComponent alerts = null;
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
@@ -57,7 +58,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
|||||||
|
|
||||||
human = entityManager.SpawnEntity("HumanMob_Content", coordinates);
|
human = entityManager.SpawnEntity("HumanMob_Content", coordinates);
|
||||||
|
|
||||||
Assert.True(human.TryGetComponent(out statusEffects));
|
Assert.True(human.TryGetComponent(out alerts));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Let WeightlessSystem and GravitySystem tick
|
// Let WeightlessSystem and GravitySystem tick
|
||||||
@@ -68,7 +69,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
|||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
// No gravity without a gravity generator
|
// No gravity without a gravity generator
|
||||||
Assert.True(statusEffects.Statuses.ContainsKey(StatusEffect.Weightless));
|
Assert.True(alerts.IsShowingAlert(AlertType.Weightless));
|
||||||
|
|
||||||
gravityGenerator = human.EnsureComponent<GravityGeneratorComponent>();
|
gravityGenerator = human.EnsureComponent<GravityGeneratorComponent>();
|
||||||
});
|
});
|
||||||
@@ -78,7 +79,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
|||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
Assert.False(statusEffects.Statuses.ContainsKey(StatusEffect.Weightless));
|
Assert.False(alerts.IsShowingAlert(AlertType.Weightless));
|
||||||
|
|
||||||
// Disable the gravity generator
|
// Disable the gravity generator
|
||||||
var args = new BreakageEventArgs {Owner = human};
|
var args = new BreakageEventArgs {Owner = human};
|
||||||
@@ -89,7 +90,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
|||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
Assert.False(statusEffects.Statuses.ContainsKey(StatusEffect.Weightless));
|
Assert.False(alerts.IsShowingAlert(AlertType.Weightless));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
Content.Server/Commands/CommandUtils.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Server.Interfaces.Console;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utilities for writing commands
|
||||||
|
/// </summary>
|
||||||
|
public static class CommandUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the player session for the player with the indicated id,
|
||||||
|
/// sending a failure to the performer if unable to.
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryGetSessionByUsernameOrId(IConsoleShell shell,
|
||||||
|
string usernameOrId, IPlayerSession performer, out IPlayerSession session)
|
||||||
|
{
|
||||||
|
var plyMgr = IoCManager.Resolve<IPlayerManager>();
|
||||||
|
if (plyMgr.TryGetSessionByUsername(usernameOrId, out session)) return true;
|
||||||
|
if (Guid.TryParse(usernameOrId, out var targetGuid))
|
||||||
|
{
|
||||||
|
if (plyMgr.TryGetSessionById(new NetUserId(targetGuid), out session)) return true;
|
||||||
|
shell.SendText(performer, "Unable to find user with that name/id.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.SendText(performer, "Unable to find user with that name/id.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the attached entity for the player session with the indicated id,
|
||||||
|
/// sending a failure to the performer if unable to.
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryGetAttachedEntityByUsernameOrId(IConsoleShell shell,
|
||||||
|
string usernameOrId, IPlayerSession performer, out IEntity attachedEntity)
|
||||||
|
{
|
||||||
|
attachedEntity = null;
|
||||||
|
if (!TryGetSessionByUsernameOrId(shell, usernameOrId, performer, out var session)) return false;
|
||||||
|
if (session.AttachedEntity == null)
|
||||||
|
{
|
||||||
|
shell.SendText(performer, "User has no attached entity.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedEntity = session.AttachedEntity;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if attached entity is null, returning false and sending a message
|
||||||
|
/// to performer if not.
|
||||||
|
/// </summary>
|
||||||
|
public static bool ValidateAttachedEntity(IConsoleShell shell, IPlayerSession performer, IEntity attachedEntity)
|
||||||
|
{
|
||||||
|
if (attachedEntity != null) return true;
|
||||||
|
shell.SendText(performer, "User has no attached entity.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ using Content.Server.Interfaces.GameTicking;
|
|||||||
using Content.Server.Interfaces.PDA;
|
using Content.Server.Interfaces.PDA;
|
||||||
using Content.Server.Sandbox;
|
using Content.Server.Sandbox;
|
||||||
using Content.Shared.Kitchen;
|
using Content.Shared.Kitchen;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Robust.Server.Interfaces.Player;
|
using Robust.Server.Interfaces.Player;
|
||||||
using Robust.Shared.ContentPack;
|
using Robust.Shared.ContentPack;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
@@ -74,6 +75,7 @@ namespace Content.Server
|
|||||||
|
|
||||||
_gameTicker.Initialize();
|
_gameTicker.Initialize();
|
||||||
IoCManager.Resolve<RecipeManager>().Initialize();
|
IoCManager.Resolve<RecipeManager>().Initialize();
|
||||||
|
IoCManager.Resolve<AlertManager>().Initialize();
|
||||||
IoCManager.Resolve<BlackboardManager>().Initialize();
|
IoCManager.Resolve<BlackboardManager>().Initialize();
|
||||||
IoCManager.Resolve<ConsiderationsManager>().Initialize();
|
IoCManager.Resolve<ConsiderationsManager>().Initialize();
|
||||||
IoCManager.Resolve<IPDAUplinkManager>().Initialize();
|
IoCManager.Resolve<IPDAUplinkManager>().Initialize();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Content.Server.GameObjects.Components.Items.Storage;
|
|||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.ActionBlocking;
|
using Content.Shared.GameObjects.Components.ActionBlocking;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
@@ -115,7 +116,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
|||||||
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
|
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
|
||||||
|
|
||||||
OnCuffedStateChanged.Invoke();
|
OnCuffedStateChanged.Invoke();
|
||||||
UpdateStatusEffect();
|
UpdateAlert();
|
||||||
UpdateHeldItems();
|
UpdateHeldItems();
|
||||||
Dirty();
|
Dirty();
|
||||||
}
|
}
|
||||||
@@ -181,17 +182,17 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the status effect indicator on the HUD.
|
/// Updates the status effect indicator on the HUD.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateStatusEffect()
|
private void UpdateAlert()
|
||||||
{
|
{
|
||||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
if (Owner.TryGetComponent(out ServerAlertsComponent status))
|
||||||
{
|
{
|
||||||
if (CanStillInteract)
|
if (CanStillInteract)
|
||||||
{
|
{
|
||||||
status.RemoveStatusEffect(StatusEffect.Cuffed);
|
status.ClearAlert(AlertType.Handcuffed);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Cuffed, "/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png");
|
status.ShowAlert(AlertType.Handcuffed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +283,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
|||||||
|
|
||||||
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
|
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
|
||||||
OnCuffedStateChanged.Invoke();
|
OnCuffedStateChanged.Invoke();
|
||||||
UpdateStatusEffect();
|
UpdateAlert();
|
||||||
Dirty();
|
Dirty();
|
||||||
|
|
||||||
if (CuffedHandCount == 0)
|
if (CuffedHandCount == 0)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.Interfaces.GameObjects;
|
using Content.Server.Interfaces.GameObjects;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
@@ -22,7 +23,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
public void Update(float airPressure)
|
public void Update(float airPressure)
|
||||||
{
|
{
|
||||||
if (!Owner.TryGetComponent(out IDamageableComponent damageable)) return;
|
if (!Owner.TryGetComponent(out IDamageableComponent damageable)) return;
|
||||||
Owner.TryGetComponent(out ServerStatusEffectsComponent status);
|
Owner.TryGetComponent(out ServerAlertsComponent status);
|
||||||
|
|
||||||
var highPressureMultiplier = 1f;
|
var highPressureMultiplier = 1f;
|
||||||
var lowPressureMultiplier = 1f;
|
var lowPressureMultiplier = 1f;
|
||||||
@@ -50,11 +51,11 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
|
|
||||||
if (pressure <= Atmospherics.HazardLowPressure)
|
if (pressure <= Atmospherics.HazardLowPressure)
|
||||||
{
|
{
|
||||||
status.ChangeStatusEffect(StatusEffect.Pressure, "/Textures/Interface/StatusEffects/Pressure/lowpressure2.png", null);
|
status.ShowAlert(AlertType.LowPressure, 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
status.ChangeStatusEffect(StatusEffect.Pressure, "/Textures/Interface/StatusEffects/Pressure/lowpressure1.png", null);
|
status.ShowAlert(AlertType.LowPressure, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// High pressure.
|
// High pressure.
|
||||||
@@ -72,16 +73,16 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
|
|
||||||
if (pressure >= Atmospherics.HazardHighPressure)
|
if (pressure >= Atmospherics.HazardHighPressure)
|
||||||
{
|
{
|
||||||
status.ChangeStatusEffect(StatusEffect.Pressure, "/Textures/Interface/StatusEffects/Pressure/highpressure2.png", null);
|
status.ShowAlert(AlertType.HighPressure, 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
status.ChangeStatusEffect(StatusEffect.Pressure, "/Textures/Interface/StatusEffects/Pressure/highpressure1.png", null);
|
status.ShowAlert(AlertType.HighPressure, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Normal pressure.
|
// Normal pressure.
|
||||||
default:
|
default:
|
||||||
status?.RemoveStatusEffect(StatusEffect.Pressure);
|
status?.ClearAlertCategory(AlertCategory.Pressure);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Content.Server.Atmos;
|
|||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.Components.Temperature;
|
using Content.Server.GameObjects.Components.Temperature;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
@@ -93,15 +94,15 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
FireStacks = MathF.Min(0, FireStacks + 1);
|
FireStacks = MathF.Min(0, FireStacks + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Owner.TryGetComponent(out ServerStatusEffectsComponent status);
|
Owner.TryGetComponent(out ServerAlertsComponent status);
|
||||||
|
|
||||||
if (!OnFire)
|
if (!OnFire)
|
||||||
{
|
{
|
||||||
status?.RemoveStatusEffect(StatusEffect.Fire);
|
status?.ClearAlert(AlertType.Fire);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
status?.ChangeStatusEffect(StatusEffect.Fire, "/Textures/Interface/StatusEffects/Fire/fire.png", null);
|
status.ShowAlert(AlertType.Fire, onClickAlert: OnClickAlert);
|
||||||
|
|
||||||
if (FireStacks > 0)
|
if (FireStacks > 0)
|
||||||
{
|
{
|
||||||
@@ -153,6 +154,14 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnClickAlert(ClickAlertEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Player.TryGetComponent(out FlammableComponent flammable))
|
||||||
|
{
|
||||||
|
flammable.Resist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void CollideWith(IEntity collidedWith)
|
public void CollideWith(IEntity collidedWith)
|
||||||
{
|
{
|
||||||
if (!collidedWith.TryGetComponent(out FlammableComponent otherFlammable))
|
if (!collidedWith.TryGetComponent(out FlammableComponent otherFlammable))
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Content.Server.GameObjects.Components.Mobs.State;
|
|||||||
using Content.Server.GameObjects.Components.Pulling;
|
using Content.Server.GameObjects.Components.Pulling;
|
||||||
using Content.Server.GameObjects.Components.Strap;
|
using Content.Server.GameObjects.Components.Strap;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Buckle;
|
using Content.Shared.GameObjects.Components.Buckle;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Strap;
|
using Content.Shared.GameObjects.Components.Strap;
|
||||||
@@ -37,7 +38,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
|||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
[ComponentDependency] public readonly AppearanceComponent? AppearanceComponent = null;
|
[ComponentDependency] public readonly AppearanceComponent? AppearanceComponent = null;
|
||||||
[ComponentDependency] private readonly ServerStatusEffectsComponent? _serverStatusEffectsComponent = null;
|
[ComponentDependency] private readonly ServerAlertsComponent? _serverAlertsComponent = null;
|
||||||
[ComponentDependency] private readonly StunnableComponent? _stunnableComponent = null;
|
[ComponentDependency] private readonly StunnableComponent? _stunnableComponent = null;
|
||||||
[ComponentDependency] private readonly MobStateManagerComponent? _mobStateManagerComponent = null;
|
[ComponentDependency] private readonly MobStateManagerComponent? _mobStateManagerComponent = null;
|
||||||
|
|
||||||
@@ -100,21 +101,31 @@ namespace Content.Server.GameObjects.Components.Buckle
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateBuckleStatus()
|
private void UpdateBuckleStatus()
|
||||||
{
|
{
|
||||||
if (_serverStatusEffectsComponent == null)
|
if (_serverAlertsComponent == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Buckled)
|
if (Buckled)
|
||||||
{
|
{
|
||||||
_serverStatusEffectsComponent.ChangeStatusEffectIcon(StatusEffect.Buckled, BuckledTo!.BuckledIcon);
|
_serverAlertsComponent.ShowAlert(BuckledTo != null ? BuckledTo.BuckledAlertType : AlertType.Buckled,
|
||||||
|
onClickAlert: OnClickAlert);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_serverStatusEffectsComponent.RemoveStatusEffect(StatusEffect.Buckled);
|
_serverAlertsComponent.ClearAlertCategory(AlertCategory.Buckled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnClickAlert(ClickAlertEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Player.TryGetComponent(out BuckleComponent? buckle))
|
||||||
|
{
|
||||||
|
buckle.TryUnbuckle(args.Player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reattaches this entity to the strap, modifying its position and rotation.
|
/// Reattaches this entity to the strap, modifying its position and rotation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Server.Commands;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Robust.Server.Interfaces.Console;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Players;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
[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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
if (EntitySystem.TryGet<WeightlessSystem>(out var weightlessSystem))
|
||||||
|
{
|
||||||
|
weightlessSystem.RemoveAlert(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.WarningS("alert", "weightlesssystem not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnRemove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ComponentState GetComponentState()
|
||||||
|
{
|
||||||
|
return new AlertsComponentState(CreateAlertStatesArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (player != Owner)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement clicking other status effects in the HUD
|
||||||
|
if (AlertManager.TryDecode(msg.EncodedAlert, out var alert))
|
||||||
|
{
|
||||||
|
PerformAlertClickCallback(alert, player);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.WarningS("alert", "unrecognized encoded alert {0}", msg.EncodedAlert);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ShowAlert : IClientCommand
|
||||||
|
{
|
||||||
|
public string Command => "showalert";
|
||||||
|
public string Description => "Shows an alert for a player, defaulting to current player";
|
||||||
|
public string Help => "showalert <alertType> <severity, -1 if no severity> <name or userID, omit for current player>";
|
||||||
|
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
||||||
|
{
|
||||||
|
var attachedEntity = player.AttachedEntity;
|
||||||
|
if (args.Length > 2)
|
||||||
|
{
|
||||||
|
var target = args[2];
|
||||||
|
if (!Commands.CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CommandUtils.ValidateAttachedEntity(shell, player, attachedEntity)) return;
|
||||||
|
|
||||||
|
|
||||||
|
if (!attachedEntity.TryGetComponent(out ServerAlertsComponent alertsComponent))
|
||||||
|
{
|
||||||
|
shell.SendText(player, "user has no alerts component");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var alertType = args[0];
|
||||||
|
var severity = args[1];
|
||||||
|
var alertMgr = IoCManager.Resolve<AlertManager>();
|
||||||
|
if (!alertMgr.TryGet(Enum.Parse<AlertType>(alertType), out var alert))
|
||||||
|
{
|
||||||
|
shell.SendText(player, "unrecognized alertType " + alertType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!short.TryParse(severity, out var sevint))
|
||||||
|
{
|
||||||
|
shell.SendText(player, "invalid severity " + sevint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alertsComponent.ShowAlert(alert.AlertType, sevint == -1 ? (short?) null : sevint);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ClearAlert : IClientCommand
|
||||||
|
{
|
||||||
|
public string Command => "clearalert";
|
||||||
|
public string Description => "Clears an alert for a player, defaulting to current player";
|
||||||
|
public string Help => "clearalert <alertType> <name or userID, omit for current player>";
|
||||||
|
|
||||||
|
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
||||||
|
{
|
||||||
|
var attachedEntity = player.AttachedEntity;
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
var target = args[1];
|
||||||
|
if (!CommandUtils.TryGetAttachedEntityByUsernameOrId(shell, target, player, out attachedEntity)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CommandUtils.ValidateAttachedEntity(shell, player, attachedEntity)) return;
|
||||||
|
|
||||||
|
if (!attachedEntity.TryGetComponent(out ServerAlertsComponent alertsComponent))
|
||||||
|
{
|
||||||
|
shell.SendText(player, "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))
|
||||||
|
{
|
||||||
|
shell.SendText(player, "unrecognized alertType " + alertType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alertsComponent.ClearAlert(alert.AlertType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.GameObjects.Components.Atmos;
|
|
||||||
using Content.Server.GameObjects.Components.Buckle;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
|
||||||
using Content.Shared.GameObjects.Components.Pulling;
|
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
|
||||||
using Content.Shared.Interfaces;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
using Robust.Shared.Interfaces.Network;
|
|
||||||
using Robust.Shared.Players;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Mobs
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
[ComponentReference(typeof(SharedStatusEffectsComponent))]
|
|
||||||
public sealed class ServerStatusEffectsComponent : SharedStatusEffectsComponent
|
|
||||||
{
|
|
||||||
[ViewVariables]
|
|
||||||
private readonly Dictionary<StatusEffect, StatusEffectStatus> _statusEffects = new Dictionary<StatusEffect, StatusEffectStatus>();
|
|
||||||
|
|
||||||
public override IReadOnlyDictionary<StatusEffect, StatusEffectStatus> Statuses => _statusEffects;
|
|
||||||
|
|
||||||
protected override void Startup()
|
|
||||||
{
|
|
||||||
base.Startup();
|
|
||||||
|
|
||||||
EntitySystem.Get<WeightlessSystem>().AddStatus(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnRemove()
|
|
||||||
{
|
|
||||||
EntitySystem.Get<WeightlessSystem>().RemoveStatus(this);
|
|
||||||
|
|
||||||
base.OnRemove();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ComponentState GetComponentState()
|
|
||||||
{
|
|
||||||
return new StatusEffectComponentState(_statusEffects);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ChangeStatusEffectIcon(StatusEffect effect, string icon)
|
|
||||||
{
|
|
||||||
if (_statusEffects.TryGetValue(effect, out var value) && value.Icon == icon)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_statusEffects[effect] = new StatusEffectStatus()
|
|
||||||
{Icon = icon, Cooldown = value.Cooldown};
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ChangeStatusEffectCooldown(StatusEffect effect, ValueTuple<TimeSpan, TimeSpan> cooldown)
|
|
||||||
{
|
|
||||||
if (_statusEffects.TryGetValue(effect, out var value)
|
|
||||||
&& value.Cooldown == cooldown)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_statusEffects[effect] = new StatusEffectStatus()
|
|
||||||
{
|
|
||||||
Icon = value.Icon, Cooldown = cooldown
|
|
||||||
};
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ChangeStatusEffect(StatusEffect effect, string icon, ValueTuple<TimeSpan, TimeSpan>? cooldown)
|
|
||||||
{
|
|
||||||
_statusEffects[effect] = new StatusEffectStatus()
|
|
||||||
{Icon = icon, Cooldown = cooldown};
|
|
||||||
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void RemoveStatusEffect(StatusEffect effect)
|
|
||||||
{
|
|
||||||
if (!_statusEffects.Remove(effect))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ClickStatusMessage msg:
|
|
||||||
{
|
|
||||||
var player = session.AttachedEntity;
|
|
||||||
|
|
||||||
if (player != Owner)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement clicking other status effects in the HUD
|
|
||||||
switch (msg.Effect)
|
|
||||||
{
|
|
||||||
case StatusEffect.Buckled:
|
|
||||||
if (!player.TryGetComponent(out BuckleComponent buckle))
|
|
||||||
break;
|
|
||||||
|
|
||||||
buckle.TryUnbuckle(player);
|
|
||||||
break;
|
|
||||||
case StatusEffect.Piloting:
|
|
||||||
if (!player.TryGetComponent(out ShuttleControllerComponent controller))
|
|
||||||
break;
|
|
||||||
|
|
||||||
controller.RemoveController();
|
|
||||||
break;
|
|
||||||
case StatusEffect.Pulling:
|
|
||||||
EntitySystem
|
|
||||||
.Get<SharedPullingSystem>()
|
|
||||||
.GetPulled(player)?
|
|
||||||
.GetComponentOrNull<SharedPullableComponent>()?
|
|
||||||
.TryStopPull();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case StatusEffect.Fire:
|
|
||||||
if (!player.TryGetComponent(out FlammableComponent flammable))
|
|
||||||
break;
|
|
||||||
|
|
||||||
flammable.Resist();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
player.PopupMessage(msg.Effect.ToString());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||||
@@ -17,10 +18,9 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
|||||||
appearance.SetData(DamageStateVisuals.State, DamageState.Critical);
|
appearance.SetData(DamageStateVisuals.State, DamageState.Critical);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
if (entity.TryGetComponent(out ServerAlertsComponent status))
|
||||||
{
|
{
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
status.ShowAlert(AlertType.HumanCrit); //Todo: combine humancrit-0 and humancrit-1 into a gif and display it
|
||||||
"/Textures/Interface/StatusEffects/Human/humancrit-0.png"); //Todo: combine humancrit-0 and humancrit-1 into a gif and display it
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||||
@@ -18,10 +19,9 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
|||||||
appearance.SetData(DamageStateVisuals.State, DamageState.Dead);
|
appearance.SetData(DamageStateVisuals.State, DamageState.Dead);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
if (entity.TryGetComponent(out ServerAlertsComponent status))
|
||||||
{
|
{
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
status.ShowAlert(AlertType.HumanDead);
|
||||||
"/Textures/Interface/StatusEffects/Human/humandead.png");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent))
|
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||||
@@ -51,9 +52,9 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
|||||||
// TODO: Might want to add an OnRemove() to IMobState since those are where these components are being used
|
// TODO: Might want to add an OnRemove() to IMobState since those are where these components are being used
|
||||||
base.OnRemove();
|
base.OnRemove();
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
if (Owner.TryGetComponent(out ServerAlertsComponent status))
|
||||||
{
|
{
|
||||||
status.RemoveStatusEffect(StatusEffect.Health);
|
status.ClearAlert(AlertType.HumanHealth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.GameObjects.Components.Damage;
|
using Content.Server.GameObjects.Components.Damage;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||||
@@ -27,15 +28,14 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
|||||||
|
|
||||||
public override void UpdateState(IEntity entity)
|
public override void UpdateState(IEntity entity)
|
||||||
{
|
{
|
||||||
if (!entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
if (!entity.TryGetComponent(out ServerAlertsComponent status))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entity.TryGetComponent(out IDamageableComponent damageable))
|
if (!entity.TryGetComponent(out IDamageableComponent damageable))
|
||||||
{
|
{
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
status.ShowAlert(AlertType.HumanHealth, 0);
|
||||||
"/Textures/Interface/StatusEffects/Human/human0.png");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +49,9 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modifier = (int) (ruinable.TotalDamage / (threshold / 7f));
|
var modifier = (short) (ruinable.TotalDamage / (threshold / 7f));
|
||||||
|
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
status.ShowAlert(AlertType.HumanHealth, modifier);
|
||||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -63,10 +62,9 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modifier = (int) (damageable.TotalDamage / (threshold / 7f));
|
var modifier = (short) (damageable.TotalDamage / (threshold / 7f));
|
||||||
|
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
status.ShowAlert(AlertType.HumanHealth, modifier);
|
||||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
@@ -89,7 +90,7 @@ namespace Content.Server.GameObjects.Components.Mobs
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!StunStart.HasValue || !StunEnd.HasValue ||
|
if (!StunStart.HasValue || !StunEnd.HasValue ||
|
||||||
!Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
!Owner.TryGetComponent(out ServerAlertsComponent status))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -102,7 +103,7 @@ namespace Content.Server.GameObjects.Components.Mobs
|
|||||||
|
|
||||||
if (progress >= length)
|
if (progress >= length)
|
||||||
{
|
{
|
||||||
Owner.SpawnTimer(250, () => status.RemoveStatusEffect(StatusEffect.Stun), StatusRemoveCancellation.Token);
|
Owner.SpawnTimer(250, () => status.ClearAlert(AlertType.Stun), StatusRemoveCancellation.Token);
|
||||||
LastStun = null;
|
LastStun = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using Content.Server.GameObjects.Components.Buckle;
|
using Content.Server.GameObjects.Components.Buckle;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
using Content.Shared.GameObjects.Components.Strap;
|
using Content.Shared.GameObjects.Components.Strap;
|
||||||
@@ -31,9 +32,9 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
private bool _movingRight;
|
private bool _movingRight;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The icon to be displayed when piloting from this chair.
|
/// ID of the alert to show when piloting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string _pilotingIcon = default!;
|
private AlertType _pilotingAlertType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity that's currently controlling this component.
|
/// The entity that's currently controlling this component.
|
||||||
@@ -137,7 +138,7 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
if (_controller != null ||
|
if (_controller != null ||
|
||||||
!entity.TryGetComponent(out MindComponent? mind) ||
|
!entity.TryGetComponent(out MindComponent? mind) ||
|
||||||
mind.Mind == null ||
|
mind.Mind == null ||
|
||||||
!Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
|
!Owner.TryGetComponent(out ServerAlertsComponent? status))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -145,7 +146,15 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
mind.Mind.Visit(Owner);
|
mind.Mind.Visit(Owner);
|
||||||
_controller = entity;
|
_controller = entity;
|
||||||
|
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Piloting, _pilotingIcon);
|
status.ShowAlert(_pilotingAlertType, onClickAlert: OnClickAlert);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClickAlert(ClickAlertEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Player.TryGetComponent(out ShuttleControllerComponent? controller))
|
||||||
|
{
|
||||||
|
controller.RemoveController();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -177,9 +186,9 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
/// <param name="entity">The entity to update</param>
|
/// <param name="entity">The entity to update</param>
|
||||||
private void UpdateRemovedEntity(IEntity entity)
|
private void UpdateRemovedEntity(IEntity entity)
|
||||||
{
|
{
|
||||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
|
if (Owner.TryGetComponent(out ServerAlertsComponent? status))
|
||||||
{
|
{
|
||||||
status.RemoveStatusEffect(StatusEffect.Piloting);
|
status.ClearAlert(_pilotingAlertType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.TryGetComponent(out MindComponent? mind))
|
if (entity.TryGetComponent(out MindComponent? mind))
|
||||||
@@ -211,13 +220,13 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
serializer.DataField(ref _pilotingIcon, "pilotingIcon", "/Textures/Interface/StatusEffects/Buckle/buckled.png");
|
serializer.DataField(ref _pilotingAlertType, "pilotingAlertType", AlertType.PilotingShuttle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
Owner.EnsureComponent<ServerStatusEffectsComponent>();
|
Owner.EnsureComponent<ServerAlertsComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
@@ -70,11 +71,11 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static readonly Dictionary<HungerThreshold, string> HungerThresholdImages = new Dictionary<HungerThreshold, string>
|
public static readonly Dictionary<HungerThreshold, AlertType> HungerThresholdAlertTypes = new Dictionary<HungerThreshold, AlertType>
|
||||||
{
|
{
|
||||||
{ HungerThreshold.Overfed, "/Textures/Interface/StatusEffects/Hunger/Overfed.png" },
|
{ HungerThreshold.Overfed, AlertType.Overfed },
|
||||||
{ HungerThreshold.Peckish, "/Textures/Interface/StatusEffects/Hunger/Peckish.png" },
|
{ HungerThreshold.Peckish, AlertType.Peckish },
|
||||||
{ HungerThreshold.Starving, "/Textures/Interface/StatusEffects/Hunger/Starving.png" },
|
{ HungerThreshold.Starving, AlertType.Starving },
|
||||||
};
|
};
|
||||||
|
|
||||||
public void HungerThresholdEffect(bool force = false)
|
public void HungerThresholdEffect(bool force = false)
|
||||||
@@ -89,15 +90,15 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
Owner.TryGetComponent(out ServerStatusEffectsComponent statusEffectsComponent);
|
Owner.TryGetComponent(out ServerAlertsComponent alertsComponent);
|
||||||
|
|
||||||
if (HungerThresholdImages.TryGetValue(_currentHungerThreshold, out var statusTexture))
|
if (HungerThresholdAlertTypes.TryGetValue(_currentHungerThreshold, out var alertId))
|
||||||
{
|
{
|
||||||
statusEffectsComponent?.ChangeStatusEffectIcon(StatusEffect.Hunger, statusTexture);
|
alertsComponent?.ShowAlert(alertId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
statusEffectsComponent?.RemoveStatusEffect(StatusEffect.Hunger);
|
alertsComponent?.ClearAlertCategory(AlertCategory.Hunger);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (_currentHungerThreshold)
|
switch (_currentHungerThreshold)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
@@ -62,11 +63,11 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
{ThirstThreshold.Dead, 0.0f},
|
{ThirstThreshold.Dead, 0.0f},
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly Dictionary<ThirstThreshold, string> ThirstThresholdImages = new Dictionary<ThirstThreshold, string>
|
public static readonly Dictionary<ThirstThreshold, AlertType> ThirstThresholdAlertTypes = new Dictionary<ThirstThreshold, AlertType>
|
||||||
{
|
{
|
||||||
{ThirstThreshold.OverHydrated, "/Textures/Interface/StatusEffects/Thirst/OverHydrated.png"},
|
{ThirstThreshold.OverHydrated, AlertType.Overhydrated},
|
||||||
{ThirstThreshold.Thirsty, "/Textures/Interface/StatusEffects/Thirst/Thirsty.png"},
|
{ThirstThreshold.Thirsty, AlertType.Thirsty},
|
||||||
{ThirstThreshold.Parched, "/Textures/Interface/StatusEffects/Thirst/Parched.png"},
|
{ThirstThreshold.Parched, AlertType.Parched},
|
||||||
};
|
};
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
@@ -87,15 +88,15 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
Owner.TryGetComponent(out ServerStatusEffectsComponent statusEffectsComponent);
|
Owner.TryGetComponent(out ServerAlertsComponent alertsComponent);
|
||||||
|
|
||||||
if (ThirstThresholdImages.TryGetValue(_currentThirstThreshold, out var statusTexture))
|
if (ThirstThresholdAlertTypes.TryGetValue(_currentThirstThreshold, out var alertId))
|
||||||
{
|
{
|
||||||
statusEffectsComponent?.ChangeStatusEffectIcon(StatusEffect.Thirst, statusTexture);
|
alertsComponent?.ShowAlert(alertId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
statusEffectsComponent?.RemoveStatusEffect(StatusEffect.Thirst);
|
alertsComponent?.ClearAlertCategory(AlertCategory.Thirst);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (_currentThirstThreshold)
|
switch (_currentThirstThreshold)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameObjects.Components.Buckle;
|
using Content.Server.GameObjects.Components.Buckle;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Strap;
|
using Content.Shared.GameObjects.Components.Strap;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.GameObjects.Verbs;
|
using Content.Shared.GameObjects.Verbs;
|
||||||
@@ -27,7 +28,7 @@ namespace Content.Server.GameObjects.Components.Strap
|
|||||||
private StrapPosition _position;
|
private StrapPosition _position;
|
||||||
private string _buckleSound = null!;
|
private string _buckleSound = null!;
|
||||||
private string _unbuckleSound = null!;
|
private string _unbuckleSound = null!;
|
||||||
private string _buckledIcon = null!;
|
private AlertType _buckledAlertType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The angle in degrees to rotate the player by when they get strapped
|
/// The angle in degrees to rotate the player by when they get strapped
|
||||||
@@ -65,10 +66,10 @@ namespace Content.Server.GameObjects.Components.Strap
|
|||||||
public string UnbuckleSound => _unbuckleSound;
|
public string UnbuckleSound => _unbuckleSound;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The icon to be displayed as a status when buckled
|
/// ID of the alert to show when buckled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public string BuckledIcon => _buckledIcon;
|
public AlertType BuckledAlertType => _buckledAlertType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sum of the sizes of all the buckled entities in this strap
|
/// The sum of the sizes of all the buckled entities in this strap
|
||||||
@@ -137,7 +138,7 @@ namespace Content.Server.GameObjects.Components.Strap
|
|||||||
serializer.DataField(ref _position, "position", StrapPosition.None);
|
serializer.DataField(ref _position, "position", StrapPosition.None);
|
||||||
serializer.DataField(ref _buckleSound, "buckleSound", "/Audio/Effects/buckle.ogg");
|
serializer.DataField(ref _buckleSound, "buckleSound", "/Audio/Effects/buckle.ogg");
|
||||||
serializer.DataField(ref _unbuckleSound, "unbuckleSound", "/Audio/Effects/unbuckle.ogg");
|
serializer.DataField(ref _unbuckleSound, "unbuckleSound", "/Audio/Effects/unbuckle.ogg");
|
||||||
serializer.DataField(ref _buckledIcon, "buckledIcon", "/Textures/Interface/StatusEffects/Buckle/buckled.png");
|
serializer.DataField(ref _buckledAlertType, "buckledAlertType", AlertType.Buckled);
|
||||||
serializer.DataField(ref _rotation, "rotation", 0);
|
serializer.DataField(ref _rotation, "rotation", 0);
|
||||||
|
|
||||||
var defaultSize = 100;
|
var defaultSize = 100;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
@@ -74,43 +75,43 @@ namespace Content.Server.GameObjects.Components.Temperature
|
|||||||
damageType = DamageType.Cold;
|
damageType = DamageType.Cold;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
if (Owner.TryGetComponent(out ServerAlertsComponent status))
|
||||||
{
|
{
|
||||||
switch(CurrentTemperature)
|
switch(CurrentTemperature)
|
||||||
{
|
{
|
||||||
// Cold strong.
|
// Cold strong.
|
||||||
case var t when t <= 260:
|
case var t when t <= 260:
|
||||||
status.ChangeStatusEffect(StatusEffect.Temperature, "/Textures/Interface/StatusEffects/Temperature/cold3.png", null);
|
status.ShowAlert(AlertType.Cold, 3);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Cold mild.
|
// Cold mild.
|
||||||
case var t when t <= 280 && t > 260:
|
case var t when t <= 280 && t > 260:
|
||||||
status.ChangeStatusEffect(StatusEffect.Temperature, "/Textures/Interface/StatusEffects/Temperature/cold2.png", null);
|
status.ShowAlert(AlertType.Cold, 2);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Cold weak.
|
// Cold weak.
|
||||||
case var t when t <= 292 && t > 280:
|
case var t when t <= 292 && t > 280:
|
||||||
status.ChangeStatusEffect(StatusEffect.Temperature, "/Textures/Interface/StatusEffects/Temperature/cold1.png", null);
|
status.ShowAlert(AlertType.Cold, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Safe.
|
// Safe.
|
||||||
case var t when t <= 327 && t > 292:
|
case var t when t <= 327 && t > 292:
|
||||||
status.RemoveStatusEffect(StatusEffect.Temperature);
|
status.ClearAlertCategory(AlertCategory.Temperature);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Heat weak.
|
// Heat weak.
|
||||||
case var t when t <= 335 && t > 327:
|
case var t when t <= 335 && t > 327:
|
||||||
status.ChangeStatusEffect(StatusEffect.Temperature, "/Textures/Interface/StatusEffects/Temperature/hot1.png", null);
|
status.ShowAlert(AlertType.Hot, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Heat mild.
|
// Heat mild.
|
||||||
case var t when t <= 345 && t > 335:
|
case var t when t <= 345 && t > 335:
|
||||||
status.ChangeStatusEffect(StatusEffect.Temperature, "/Textures/Interface/StatusEffects/Temperature/hot2.png", null);
|
status.ShowAlert(AlertType.Hot, 2);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Heat strong.
|
// Heat strong.
|
||||||
case var t when t > 345:
|
case var t when t > 345:
|
||||||
status.ChangeStatusEffect(StatusEffect.Temperature, "/Textures/Interface/StatusEffects/Temperature/hot3.png", null);
|
status.ShowAlert(AlertType.Hot, 3);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.EntitySystemMessages.Gravity;
|
using Content.Shared.GameObjects.EntitySystemMessages.Gravity;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
@@ -19,7 +20,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
|
||||||
private readonly Dictionary<GridId, List<ServerStatusEffectsComponent>> _statuses = new Dictionary<GridId, List<ServerStatusEffectsComponent>>();
|
private readonly Dictionary<GridId, List<ServerAlertsComponent>> _alerts = new Dictionary<GridId, List<ServerAlertsComponent>>();
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -31,15 +32,15 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_statuses.Clear();
|
_alerts.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddStatus(ServerStatusEffectsComponent status)
|
public void AddAlert(ServerAlertsComponent status)
|
||||||
{
|
{
|
||||||
var gridId = status.Owner.Transform.GridID;
|
var gridId = status.Owner.Transform.GridID;
|
||||||
var statuses = _statuses.GetOrNew(gridId);
|
var alerts = _alerts.GetOrNew(gridId);
|
||||||
|
|
||||||
statuses.Add(status);
|
alerts.Add(status);
|
||||||
|
|
||||||
if (_mapManager.TryGetGrid(status.Owner.Transform.GridID, out var grid))
|
if (_mapManager.TryGetGrid(status.Owner.Transform.GridID, out var grid))
|
||||||
{
|
{
|
||||||
@@ -54,10 +55,10 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveStatus(ServerStatusEffectsComponent status)
|
public void RemoveAlert(ServerAlertsComponent status)
|
||||||
{
|
{
|
||||||
var grid = status.Owner.Transform.GridID;
|
var grid = status.Owner.Transform.GridID;
|
||||||
if (!_statuses.TryGetValue(grid, out var statuses))
|
if (!_alerts.TryGetValue(grid, out var statuses))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -67,7 +68,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
|
|
||||||
private void GravityChanged(GravityChangedMessage ev)
|
private void GravityChanged(GravityChangedMessage ev)
|
||||||
{
|
{
|
||||||
if (!_statuses.TryGetValue(ev.Grid.Index, out var statuses))
|
if (!_alerts.TryGetValue(ev.Grid.Index, out var statuses))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -88,19 +89,19 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddWeightless(ServerStatusEffectsComponent status)
|
private void AddWeightless(ServerAlertsComponent status)
|
||||||
{
|
{
|
||||||
status.ChangeStatusEffect(StatusEffect.Weightless, "/Textures/Interface/StatusEffects/Weightless/weightless.png", null);
|
status.ShowAlert(AlertType.Weightless);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveWeightless(ServerStatusEffectsComponent status)
|
private void RemoveWeightless(ServerAlertsComponent status)
|
||||||
{
|
{
|
||||||
status.RemoveStatusEffect(StatusEffect.Weightless);
|
status.ClearAlert(AlertType.Weightless);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EntParentChanged(EntParentChangedMessage ev)
|
private void EntParentChanged(EntParentChangedMessage ev)
|
||||||
{
|
{
|
||||||
if (!ev.Entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
if (!ev.Entity.TryGetComponent(out ServerAlertsComponent status))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -110,14 +111,14 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
{
|
{
|
||||||
var oldGrid = mapGrid.GridIndex;
|
var oldGrid = mapGrid.GridIndex;
|
||||||
|
|
||||||
if (_statuses.TryGetValue(oldGrid, out var oldStatuses))
|
if (_alerts.TryGetValue(oldGrid, out var oldStatuses))
|
||||||
{
|
{
|
||||||
oldStatuses.Remove(status);
|
oldStatuses.Remove(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newGrid = ev.Entity.Transform.GridID;
|
var newGrid = ev.Entity.Transform.GridID;
|
||||||
var newStatuses = _statuses.GetOrNew(newGrid);
|
var newStatuses = _alerts.GetOrNew(newGrid);
|
||||||
|
|
||||||
newStatuses.Add(status);
|
newStatuses.Add(status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using Content.Server.Sandbox;
|
|||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Kitchen;
|
using Content.Shared.Kitchen;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
namespace Content.Server
|
namespace Content.Server
|
||||||
@@ -37,6 +38,7 @@ namespace Content.Server
|
|||||||
IoCManager.Register<IServerPreferencesManager, ServerPreferencesManager>();
|
IoCManager.Register<IServerPreferencesManager, ServerPreferencesManager>();
|
||||||
IoCManager.Register<IServerDbManager, ServerDbManager>();
|
IoCManager.Register<IServerDbManager, ServerDbManager>();
|
||||||
IoCManager.Register<RecipeManager, RecipeManager>();
|
IoCManager.Register<RecipeManager, RecipeManager>();
|
||||||
|
IoCManager.Register<AlertManager, AlertManager>();
|
||||||
IoCManager.Register<IPDAUplinkManager,PDAUplinkManager>();
|
IoCManager.Register<IPDAUplinkManager,PDAUplinkManager>();
|
||||||
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
|
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
|
||||||
IoCManager.Register<INodeGroupManager, NodeGroupManager>();
|
IoCManager.Register<INodeGroupManager, NodeGroupManager>();
|
||||||
|
|||||||
124
Content.Shared/Alert/AlertManager.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Prototypes.Kitchen;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Alert
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to all configured alerts. Ability to encode/decode a given state
|
||||||
|
/// to an int.
|
||||||
|
/// </summary>
|
||||||
|
public class AlertManager
|
||||||
|
{
|
||||||
|
[Dependency]
|
||||||
|
private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
|
private AlertPrototype[] _orderedAlerts;
|
||||||
|
private Dictionary<AlertType, byte> _typeToIndex;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
// order by type value so we can map between the id and an integer index and use
|
||||||
|
// the index for compact alert change messages
|
||||||
|
_orderedAlerts =
|
||||||
|
_prototypeManager.EnumeratePrototypes<AlertPrototype>()
|
||||||
|
.OrderBy(prototype => prototype.AlertType).ToArray();
|
||||||
|
_typeToIndex = new Dictionary<AlertType, byte>();
|
||||||
|
|
||||||
|
for (var i = 0; i < _orderedAlerts.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > byte.MaxValue)
|
||||||
|
{
|
||||||
|
Logger.ErrorS("alert", "too many alerts for byte encoding ({0})! encoding will need" +
|
||||||
|
" to be changed to use a ushort rather than byte", _typeToIndex.Count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!_typeToIndex.TryAdd(_orderedAlerts[i].AlertType, (byte) i))
|
||||||
|
{
|
||||||
|
Logger.ErrorS("alert",
|
||||||
|
"Found alert with duplicate id {0}", _orderedAlerts[i].AlertType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the alert of the indicated type
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if found</returns>
|
||||||
|
public bool TryGet(AlertType alertType, out AlertPrototype alert)
|
||||||
|
{
|
||||||
|
if (_typeToIndex.TryGetValue(alertType, out var idx))
|
||||||
|
{
|
||||||
|
alert = _orderedAlerts[idx];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the alert of the indicated type along with its encoding
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if found</returns>
|
||||||
|
public bool TryGetWithEncoded(AlertType alertType, out AlertPrototype alert, out byte encoded)
|
||||||
|
{
|
||||||
|
if (_typeToIndex.TryGetValue(alertType, out var idx))
|
||||||
|
{
|
||||||
|
alert = _orderedAlerts[idx];
|
||||||
|
encoded = (byte) idx;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert = null;
|
||||||
|
encoded = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the compact encoded representation of this alert
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if successful</returns>
|
||||||
|
public bool TryEncode(AlertPrototype alert, out byte encoded)
|
||||||
|
{
|
||||||
|
return TryEncode(alert.AlertType, out encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the compact encoded representation of the alert with
|
||||||
|
/// the indicated id
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if successful</returns>
|
||||||
|
public bool TryEncode(AlertType alertType, out byte encoded)
|
||||||
|
{
|
||||||
|
if (_typeToIndex.TryGetValue(alertType, out var idx))
|
||||||
|
{
|
||||||
|
encoded = idx;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the alert from the encoded representation
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if successful</returns>
|
||||||
|
public bool TryDecode(byte encodedAlert, out AlertPrototype alert)
|
||||||
|
{
|
||||||
|
if (encodedAlert >= _orderedAlerts.Length)
|
||||||
|
{
|
||||||
|
alert = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert = _orderedAlerts[encodedAlert];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Content.Shared/Alert/AlertOrderPrototype.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Shared.Alert
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the order of alerts so they show up in a consistent order.
|
||||||
|
/// </summary>
|
||||||
|
[Prototype("alertOrder")]
|
||||||
|
public class AlertOrderPrototype : IPrototype, IComparer<AlertPrototype>
|
||||||
|
{
|
||||||
|
private Dictionary<AlertType, int> _typeToIdx = new Dictionary<AlertType, int>();
|
||||||
|
private Dictionary<AlertCategory, int> _categoryToIdx = new Dictionary<AlertCategory, int>();
|
||||||
|
|
||||||
|
public void LoadFrom(YamlMappingNode mapping)
|
||||||
|
{
|
||||||
|
if (!mapping.TryGetNode("order", out YamlSequenceNode orderMapping)) return;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
foreach (var entryYaml in orderMapping)
|
||||||
|
{
|
||||||
|
var orderEntry = (YamlMappingNode) entryYaml;
|
||||||
|
var serializer = YamlObjectSerializer.NewReader(orderEntry);
|
||||||
|
if (serializer.TryReadDataField("category", out AlertCategory alertCategory))
|
||||||
|
{
|
||||||
|
_categoryToIdx[alertCategory] = i++;
|
||||||
|
}
|
||||||
|
else if (serializer.TryReadDataField("alertType", out AlertType alertType))
|
||||||
|
{
|
||||||
|
_typeToIdx[alertType] = i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetOrderIndex(AlertPrototype alert)
|
||||||
|
{
|
||||||
|
if (_typeToIdx.TryGetValue(alert.AlertType, out var idx))
|
||||||
|
{
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
if (alert.Category != null &&
|
||||||
|
_categoryToIdx.TryGetValue((AlertCategory) alert.Category, out idx))
|
||||||
|
{
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Compare(AlertPrototype x, AlertPrototype y)
|
||||||
|
{
|
||||||
|
if ((x == null) && (y == null)) return 0;
|
||||||
|
if (x == null) return 1;
|
||||||
|
if (y == null) return -1;
|
||||||
|
var idx = GetOrderIndex(x);
|
||||||
|
var idy = GetOrderIndex(y);
|
||||||
|
if (idx == -1 && idy == -1)
|
||||||
|
{
|
||||||
|
// break ties by type value
|
||||||
|
return x.AlertType - y.AlertType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx == -1) return 1;
|
||||||
|
if (idy == -1) return -1;
|
||||||
|
var result = idx - idy;
|
||||||
|
// not strictly necessary (we don't care about ones that go at the same index)
|
||||||
|
// but it makes the sort stable
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
// break ties by type value
|
||||||
|
return x.AlertType - y.AlertType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
189
Content.Shared/Alert/AlertPrototype.cs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Shared.Alert
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An alert popup with associated icon, tooltip, and other data.
|
||||||
|
/// </summary>
|
||||||
|
[Prototype("alert")]
|
||||||
|
public class AlertPrototype : IPrototype
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type of alert, no 2 alert prototypes should have the same one.
|
||||||
|
/// </summary>
|
||||||
|
public AlertType AlertType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the icon (png) to show in alert bar. If severity levels are supported,
|
||||||
|
/// this should be the path to the icon without the severity number
|
||||||
|
/// (i.e. hot.png if there is hot1.png and hot2.png). Use <see cref="GetIconPath"/>
|
||||||
|
/// to get the correct icon path for a particular severity level.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string IconPath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name to show in tooltip window. Accepts formatting.
|
||||||
|
/// </summary>
|
||||||
|
public FormattedMessage Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Description to show in tooltip window. Accepts formatting.
|
||||||
|
/// </summary>
|
||||||
|
public FormattedMessage Description { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Category the alert belongs to. Only one alert of a given category
|
||||||
|
/// can be shown at a time. If one is shown while another is already being shown,
|
||||||
|
/// it will be replaced. This can be useful for categories of alerts which should naturally
|
||||||
|
/// replace each other and are mutually exclusive, for example lowpressure / highpressure,
|
||||||
|
/// hot / cold. If left unspecified, the alert will not replace or be replaced by any other alerts.
|
||||||
|
/// </summary>
|
||||||
|
public AlertCategory? Category { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key which is unique w.r.t category semantics (alerts with same category have equal keys,
|
||||||
|
/// alerts with no category have different keys).
|
||||||
|
/// </summary>
|
||||||
|
public AlertKey AlertKey { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -1 (no effect) unless MaxSeverity is specified. Defaults to 1. Minimum severity level supported by this state.
|
||||||
|
/// </summary>
|
||||||
|
public short MinSeverity => MaxSeverity == -1 ? (short) -1 : _minSeverity;
|
||||||
|
private short _minSeverity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum severity level supported by this state. -1 (default) indicates
|
||||||
|
/// no severity levels are supported by the state.
|
||||||
|
/// </summary>
|
||||||
|
public short MaxSeverity { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this state support severity levels
|
||||||
|
/// </summary>
|
||||||
|
public bool SupportsSeverity => MaxSeverity != -1;
|
||||||
|
|
||||||
|
public void LoadFrom(YamlMappingNode mapping)
|
||||||
|
{
|
||||||
|
var serializer = YamlObjectSerializer.NewReader(mapping);
|
||||||
|
|
||||||
|
serializer.DataField(this, x => x.IconPath, "icon", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.MaxSeverity, "maxSeverity", (short) -1);
|
||||||
|
serializer.DataField(ref _minSeverity, "minSeverity", (short) 1);
|
||||||
|
|
||||||
|
serializer.DataReadFunction("name", string.Empty,
|
||||||
|
s => Name = FormattedMessage.FromMarkup(s));
|
||||||
|
serializer.DataReadFunction("description", string.Empty,
|
||||||
|
s => Description = FormattedMessage.FromMarkup(s));
|
||||||
|
|
||||||
|
serializer.DataField(this, x => x.AlertType, "alertType", AlertType.Error);
|
||||||
|
if (AlertType == AlertType.Error)
|
||||||
|
{
|
||||||
|
Logger.ErrorS("alert", "missing or invalid alertType for alert with name {0}", Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serializer.TryReadDataField("category", out AlertCategory alertCategory))
|
||||||
|
{
|
||||||
|
Category = alertCategory;
|
||||||
|
}
|
||||||
|
AlertKey = new AlertKey(AlertType, Category);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="severity">severity level, if supported by this alert</param>
|
||||||
|
/// <returns>the icon path to the texture for the provided severity level</returns>
|
||||||
|
public string GetIconPath(short? severity = null)
|
||||||
|
{
|
||||||
|
if (!SupportsSeverity && severity != null)
|
||||||
|
{
|
||||||
|
Logger.WarningS("alert", "attempted to get icon path for severity level for alert {0}, but" +
|
||||||
|
" this alert does not support severity levels", AlertType);
|
||||||
|
}
|
||||||
|
if (!SupportsSeverity) return IconPath;
|
||||||
|
if (severity == null)
|
||||||
|
{
|
||||||
|
Logger.WarningS("alert", "attempted to get icon path without severity level for alert {0}," +
|
||||||
|
" but this alert requires a severity level. Using lowest" +
|
||||||
|
" valid severity level instead...", AlertType);
|
||||||
|
severity = MinSeverity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (severity < MinSeverity)
|
||||||
|
{
|
||||||
|
Logger.WarningS("alert", "attempted to get icon path with severity level {0} for alert {1}," +
|
||||||
|
" but the minimum severity level for this alert is {2}. Using" +
|
||||||
|
" lowest valid severity level instead...", severity, AlertType, MinSeverity);
|
||||||
|
severity = MinSeverity;
|
||||||
|
}
|
||||||
|
if (severity > MaxSeverity)
|
||||||
|
{
|
||||||
|
Logger.WarningS("alert", "attempted to get icon path with severity level {0} for alert {1}," +
|
||||||
|
" but the max severity level for this alert is {2}. Using" +
|
||||||
|
" highest valid severity level instead...", severity, AlertType, MaxSeverity);
|
||||||
|
severity = MaxSeverity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// split and add the severity number to the path
|
||||||
|
var ext = IconPath.LastIndexOf('.');
|
||||||
|
return IconPath.Substring(0, ext) + severity + IconPath.Substring(ext, IconPath.Length - ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
{
|
||||||
|
private readonly AlertType? _alertType;
|
||||||
|
private 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 ID, and you
|
||||||
|
/// compare this to another AlertKey that has both the category and the same ID, it will not consider them equal.
|
||||||
|
public AlertKey(AlertType? alertType, AlertCategory? alertCategory)
|
||||||
|
{
|
||||||
|
// if there is a category, ignore the alerttype.
|
||||||
|
if (alertCategory != null)
|
||||||
|
{
|
||||||
|
_alertCategory = alertCategory;
|
||||||
|
_alertType = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_alertCategory = null;
|
||||||
|
_alertType = alertType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(AlertKey other)
|
||||||
|
{
|
||||||
|
return _alertType == other._alertType && _alertCategory == other._alertCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is AlertKey other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(_alertType, _alertCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="category">alert category, must not be null</param>
|
||||||
|
/// <returns>An alert key for the provided alert category</returns>
|
||||||
|
public static AlertKey ForCategory(AlertCategory category)
|
||||||
|
{
|
||||||
|
return new AlertKey(null, category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Content.Shared/Alert/AlertType.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
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,
|
||||||
|
Buckled,
|
||||||
|
Health,
|
||||||
|
Piloting,
|
||||||
|
Hunger,
|
||||||
|
Thirst
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML
|
||||||
|
/// </summary>
|
||||||
|
public enum AlertType
|
||||||
|
{
|
||||||
|
Error,
|
||||||
|
LowPressure,
|
||||||
|
HighPressure,
|
||||||
|
Fire,
|
||||||
|
Cold,
|
||||||
|
Hot,
|
||||||
|
Weightless,
|
||||||
|
Stun,
|
||||||
|
Handcuffed,
|
||||||
|
Buckled,
|
||||||
|
HumanCrit,
|
||||||
|
HumanDead,
|
||||||
|
HumanHealth,
|
||||||
|
PilotingShuttle,
|
||||||
|
Overfed,
|
||||||
|
Peckish,
|
||||||
|
Starving,
|
||||||
|
Overhydrated,
|
||||||
|
Thirsty,
|
||||||
|
Parched,
|
||||||
|
Pulled,
|
||||||
|
Pulling,
|
||||||
|
Debug1,
|
||||||
|
Debug2,
|
||||||
|
Debug3,
|
||||||
|
Debug4,
|
||||||
|
Debug5,
|
||||||
|
Debug6
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the icons on the right side of the screen.
|
||||||
|
/// Should only be used for player-controlled entities.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SharedAlertsComponent : Component
|
||||||
|
{
|
||||||
|
private static readonly AlertState[] NO_ALERTS = new AlertState[0];
|
||||||
|
|
||||||
|
[Dependency]
|
||||||
|
protected readonly AlertManager AlertManager = default!;
|
||||||
|
|
||||||
|
public override string Name => "AlertsUI";
|
||||||
|
public override uint? NetID => ContentNetIDs.ALERTS;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private Dictionary<AlertKey, ClickableAlertState> _alerts = new Dictionary<AlertKey, ClickableAlertState>();
|
||||||
|
|
||||||
|
/// <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<AlertState> EnumerateAlertStates()
|
||||||
|
{
|
||||||
|
return _alerts.Values.Select(alertData => alertData.AlertState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the alert's specified callback if there is one.
|
||||||
|
/// Not intended to be used on clientside.
|
||||||
|
/// </summary>
|
||||||
|
protected void PerformAlertClickCallback(AlertPrototype alert, IEntity owner)
|
||||||
|
{
|
||||||
|
if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback))
|
||||||
|
{
|
||||||
|
alertStateCallback.OnClickAlert?.Invoke(new ClickAlertEventArgs(owner, alert));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.DebugS("alert", "player {0} attempted to invoke" +
|
||||||
|
" alert click for {1} but that alert is not currently" +
|
||||||
|
" showing", owner.Name, alert.AlertType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new array containing all of the current alert states.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected AlertState[] CreateAlertStatesArray()
|
||||||
|
{
|
||||||
|
if (_alerts.Count == 0) return NO_ALERTS;
|
||||||
|
var states = new AlertState[_alerts.Count];
|
||||||
|
// because I don't trust LINQ
|
||||||
|
var idx = 0;
|
||||||
|
foreach (var alertData in _alerts.Values)
|
||||||
|
{
|
||||||
|
states[idx++] = alertData.AlertState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return states;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool TryGetAlertState(AlertKey key, out AlertState alertState)
|
||||||
|
{
|
||||||
|
if (_alerts.TryGetValue(key, out var alertData))
|
||||||
|
{
|
||||||
|
alertState = alertData.AlertState;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
alertState = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replace the current active alerts with the specified alerts. Any
|
||||||
|
/// OnClickAlert callbacks on the active alerts will be erased.
|
||||||
|
/// </summary>
|
||||||
|
protected void SetAlerts(AlertState[] alerts)
|
||||||
|
{
|
||||||
|
var newAlerts = new Dictionary<AlertKey, ClickableAlertState>();
|
||||||
|
foreach (var alertState in alerts)
|
||||||
|
{
|
||||||
|
if (AlertManager.TryDecode(alertState.AlertEncoded, out var alert))
|
||||||
|
{
|
||||||
|
newAlerts[alert.AlertKey] = new ClickableAlertState
|
||||||
|
{
|
||||||
|
AlertState = alertState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.ErrorS("alert", "unrecognized encoded alert {0}", alertState.AlertEncoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_alerts = newAlerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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="onClickAlert">callback to invoke when ClickAlertMessage is received by the server
|
||||||
|
/// after being clicked by client. Has no effect when specified on the clientside.</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, OnClickAlert onClickAlert = null,
|
||||||
|
ValueTuple<TimeSpan, TimeSpan>? cooldown = null)
|
||||||
|
{
|
||||||
|
if (AlertManager.TryGetWithEncoded(alertType, out var alert, out var encoded))
|
||||||
|
{
|
||||||
|
if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) &&
|
||||||
|
alertStateCallback.AlertState.AlertEncoded == encoded &&
|
||||||
|
alertStateCallback.AlertState.Severity == severity && alertStateCallback.AlertState.Cooldown == cooldown)
|
||||||
|
{
|
||||||
|
alertStateCallback.OnClickAlert = onClickAlert;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_alerts[alert.AlertKey] = new ClickableAlertState
|
||||||
|
{
|
||||||
|
AlertState = new AlertState
|
||||||
|
{Cooldown = cooldown, AlertEncoded = encoded, Severity = severity},
|
||||||
|
OnClickAlert = onClickAlert
|
||||||
|
};
|
||||||
|
|
||||||
|
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 clearing an alert prior to dirtying the control
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void AfterClearAlert() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class AlertsComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public AlertState[] Alerts;
|
||||||
|
|
||||||
|
public AlertsComponentState(AlertState[] alerts) : base(ContentNetIDs.ALERTS)
|
||||||
|
{
|
||||||
|
Alerts = alerts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A message that calls the click interaction on a alert
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class ClickAlertMessage : ComponentMessage
|
||||||
|
{
|
||||||
|
public readonly byte EncodedAlert;
|
||||||
|
|
||||||
|
public ClickAlertMessage(byte encodedAlert)
|
||||||
|
{
|
||||||
|
Directed = true;
|
||||||
|
EncodedAlert = encodedAlert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public struct AlertState
|
||||||
|
{
|
||||||
|
public byte AlertEncoded;
|
||||||
|
public short? Severity;
|
||||||
|
public ValueTuple<TimeSpan, TimeSpan>? Cooldown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ClickableAlertState
|
||||||
|
{
|
||||||
|
public AlertState AlertState;
|
||||||
|
public OnClickAlert OnClickAlert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void OnClickAlert(ClickAlertEventArgs args);
|
||||||
|
|
||||||
|
public class ClickAlertEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Player clicking the alert
|
||||||
|
/// </summary>
|
||||||
|
public readonly IEntity Player;
|
||||||
|
/// <summary>
|
||||||
|
/// Alert that was clicked
|
||||||
|
/// </summary>
|
||||||
|
public readonly AlertPrototype Alert;
|
||||||
|
|
||||||
|
public ClickAlertEventArgs(IEntity player, AlertPrototype alert)
|
||||||
|
{
|
||||||
|
Player = player;
|
||||||
|
Alert = alert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.GameObjects.Components.Mobs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the icons on the right side of the screen.
|
|
||||||
/// Should only be used for player-controlled entities
|
|
||||||
/// </summary>
|
|
||||||
public abstract class SharedStatusEffectsComponent : Component
|
|
||||||
{
|
|
||||||
public override string Name => "StatusEffectsUI";
|
|
||||||
public override uint? NetID => ContentNetIDs.STATUSEFFECTS;
|
|
||||||
|
|
||||||
public abstract IReadOnlyDictionary<StatusEffect, StatusEffectStatus> Statuses { get; }
|
|
||||||
|
|
||||||
public abstract void ChangeStatusEffectIcon(StatusEffect effect, string icon);
|
|
||||||
|
|
||||||
public abstract void ChangeStatusEffect(StatusEffect effect, string icon, ValueTuple<TimeSpan, TimeSpan>? cooldown);
|
|
||||||
|
|
||||||
public abstract void RemoveStatusEffect(StatusEffect effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class StatusEffectComponentState : ComponentState
|
|
||||||
{
|
|
||||||
public Dictionary<StatusEffect, StatusEffectStatus> StatusEffects;
|
|
||||||
|
|
||||||
public StatusEffectComponentState(Dictionary<StatusEffect, StatusEffectStatus> statusEffects) : base(ContentNetIDs.STATUSEFFECTS)
|
|
||||||
{
|
|
||||||
StatusEffects = statusEffects;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A message that calls the click interaction on a status effect
|
|
||||||
/// </summary>
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class ClickStatusMessage : ComponentMessage
|
|
||||||
{
|
|
||||||
public readonly StatusEffect Effect;
|
|
||||||
|
|
||||||
public ClickStatusMessage(StatusEffect effect)
|
|
||||||
{
|
|
||||||
Directed = true;
|
|
||||||
Effect = effect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public struct StatusEffectStatus
|
|
||||||
{
|
|
||||||
public string Icon;
|
|
||||||
public ValueTuple<TimeSpan, TimeSpan>? Cooldown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each status effect is assumed to be unique
|
|
||||||
public enum StatusEffect
|
|
||||||
{
|
|
||||||
Health,
|
|
||||||
Hunger,
|
|
||||||
Thirst,
|
|
||||||
Pressure,
|
|
||||||
Fire,
|
|
||||||
Temperature,
|
|
||||||
Stun,
|
|
||||||
Cuffed,
|
|
||||||
Buckled,
|
|
||||||
Piloting,
|
|
||||||
Pulling,
|
|
||||||
Pulled,
|
|
||||||
Weightless
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
@@ -41,7 +42,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
|
|||||||
protected float KnockdownTimer;
|
protected float KnockdownTimer;
|
||||||
protected float SlowdownTimer;
|
protected float SlowdownTimer;
|
||||||
|
|
||||||
private string _stunTexture;
|
private string _stunAlertId;
|
||||||
|
|
||||||
protected CancellationTokenSource StatusRemoveCancellation = new CancellationTokenSource();
|
protected CancellationTokenSource StatusRemoveCancellation = new CancellationTokenSource();
|
||||||
|
|
||||||
@@ -117,7 +118,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
|
|||||||
StunnedTimer = seconds;
|
StunnedTimer = seconds;
|
||||||
LastStun = _gameTiming.CurTime;
|
LastStun = _gameTiming.CurTime;
|
||||||
|
|
||||||
SetStatusEffect();
|
SetAlert();
|
||||||
OnStun();
|
OnStun();
|
||||||
|
|
||||||
Dirty();
|
Dirty();
|
||||||
@@ -144,7 +145,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
|
|||||||
KnockdownTimer = seconds;
|
KnockdownTimer = seconds;
|
||||||
LastStun = _gameTiming.CurTime;
|
LastStun = _gameTiming.CurTime;
|
||||||
|
|
||||||
SetStatusEffect();
|
SetAlert();
|
||||||
OnKnockdown();
|
OnKnockdown();
|
||||||
|
|
||||||
Dirty();
|
Dirty();
|
||||||
@@ -186,18 +187,18 @@ namespace Content.Shared.GameObjects.Components.Mobs
|
|||||||
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
|
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
|
||||||
movement.RefreshMovementSpeedModifiers();
|
movement.RefreshMovementSpeedModifiers();
|
||||||
|
|
||||||
SetStatusEffect();
|
SetAlert();
|
||||||
Dirty();
|
Dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetStatusEffect()
|
private void SetAlert()
|
||||||
{
|
{
|
||||||
if (!Owner.TryGetComponent(out SharedStatusEffectsComponent status))
|
if (!Owner.TryGetComponent(out SharedAlertsComponent status))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
status.ChangeStatusEffect(StatusEffect.Stun, _stunTexture,
|
status.ShowAlert(AlertType.Stun, cooldown:
|
||||||
(StunStart == null || StunEnd == null) ? default : (StunStart.Value, StunEnd.Value));
|
(StunStart == null || StunEnd == null) ? default : (StunStart.Value, StunEnd.Value));
|
||||||
StatusRemoveCancellation.Cancel();
|
StatusRemoveCancellation.Cancel();
|
||||||
StatusRemoveCancellation = new CancellationTokenSource();
|
StatusRemoveCancellation = new CancellationTokenSource();
|
||||||
@@ -212,8 +213,8 @@ namespace Content.Shared.GameObjects.Components.Mobs
|
|||||||
serializer.DataField(ref _slowdownCap, "slowdownCap", 20f);
|
serializer.DataField(ref _slowdownCap, "slowdownCap", 20f);
|
||||||
serializer.DataField(ref _helpInterval, "helpInterval", 1f);
|
serializer.DataField(ref _helpInterval, "helpInterval", 1f);
|
||||||
serializer.DataField(ref _helpKnockdownRemove, "helpKnockdownRemove", 1f);
|
serializer.DataField(ref _helpKnockdownRemove, "helpKnockdownRemove", 1f);
|
||||||
serializer.DataField(ref _stunTexture, "stunTexture",
|
serializer.DataField(ref _stunAlertId, "stunAlertId",
|
||||||
"/Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png");
|
"stun");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnInteractHand() { }
|
protected virtual void OnInteractHand() { }
|
||||||
@@ -230,7 +231,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
|
|||||||
|
|
||||||
KnockdownTimer -= _helpKnockdownRemove;
|
KnockdownTimer -= _helpKnockdownRemove;
|
||||||
|
|
||||||
SetStatusEffect();
|
SetAlert();
|
||||||
Dirty();
|
Dirty();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
using Content.Shared.Physics.Pull;
|
using Content.Shared.Physics.Pull;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.ComponentDependencies;
|
using Robust.Shared.GameObjects.ComponentDependencies;
|
||||||
using Robust.Shared.GameObjects.Components;
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
@@ -204,29 +207,36 @@ namespace Content.Shared.GameObjects.Components.Pulling
|
|||||||
|
|
||||||
private void AddPullingStatuses(IEntity puller)
|
private void AddPullingStatuses(IEntity puller)
|
||||||
{
|
{
|
||||||
if (Owner.TryGetComponent(out SharedStatusEffectsComponent? pulledStatus))
|
if (Owner.TryGetComponent(out SharedAlertsComponent? pulledStatus))
|
||||||
{
|
{
|
||||||
pulledStatus.ChangeStatusEffectIcon(StatusEffect.Pulled,
|
pulledStatus.ShowAlert(AlertType.Pulled);
|
||||||
"/Textures/Interface/StatusEffects/Pull/pulled.png");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (puller.TryGetComponent(out SharedStatusEffectsComponent? ownerStatus))
|
if (puller.TryGetComponent(out SharedAlertsComponent? ownerStatus))
|
||||||
{
|
{
|
||||||
ownerStatus.ChangeStatusEffectIcon(StatusEffect.Pulling,
|
ownerStatus.ShowAlert(AlertType.Pulling, onClickAlert: OnClickAlert);
|
||||||
"/Textures/Interface/StatusEffects/Pull/pulling.png");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnClickAlert(ClickAlertEventArgs args)
|
||||||
|
{
|
||||||
|
EntitySystem
|
||||||
|
.Get<SharedPullingSystem>()
|
||||||
|
.GetPulled(args.Player)?
|
||||||
|
.GetComponentOrNull<SharedPullableComponent>()?
|
||||||
|
.TryStopPull();
|
||||||
|
}
|
||||||
|
|
||||||
private void RemovePullingStatuses(IEntity puller)
|
private void RemovePullingStatuses(IEntity puller)
|
||||||
{
|
{
|
||||||
if (Owner.TryGetComponent(out SharedStatusEffectsComponent? pulledStatus))
|
if (Owner.TryGetComponent(out SharedAlertsComponent? pulledStatus))
|
||||||
{
|
{
|
||||||
pulledStatus.RemoveStatusEffect(StatusEffect.Pulled);
|
pulledStatus.ClearAlert(AlertType.Pulled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (puller.TryGetComponent(out SharedStatusEffectsComponent? ownerStatus))
|
if (puller.TryGetComponent(out SharedAlertsComponent? ownerStatus))
|
||||||
{
|
{
|
||||||
ownerStatus.RemoveStatusEffect(StatusEffect.Pulling);
|
ownerStatus.ClearAlert(AlertType.Pulling);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
public const uint RESEARCH_CONSOLE = 1023;
|
public const uint RESEARCH_CONSOLE = 1023;
|
||||||
public const uint WIRES = 1024;
|
public const uint WIRES = 1024;
|
||||||
public const uint COMBATMODE = 1025;
|
public const uint COMBATMODE = 1025;
|
||||||
public const uint STATUSEFFECTS = 1026;
|
public const uint ALERTS = 1026;
|
||||||
public const uint OVERLAYEFFECTS = 1027;
|
public const uint OVERLAYEFFECTS = 1027;
|
||||||
public const uint STOMACH = 1028;
|
public const uint STOMACH = 1028;
|
||||||
public const uint ITEMCOOLDOWN = 1029;
|
public const uint ITEMCOOLDOWN = 1029;
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Utility;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Tests.Server.GameObjects.Components.Mobs
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[TestOf(typeof(ServerAlertsComponent))]
|
||||||
|
public class ServerAlertsComponentTests : ContentUnitTest
|
||||||
|
{
|
||||||
|
const string PROTOTYPES = @"
|
||||||
|
- type: alert
|
||||||
|
alertType: LowPressure
|
||||||
|
category: Pressure
|
||||||
|
icon: /Textures/Interface/Alerts/Pressure/lowpressure.png
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: HighPressure
|
||||||
|
category: Pressure
|
||||||
|
icon: /Textures/Interface/Alerts/Pressure/highpressure.png
|
||||||
|
";
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ShowAlerts()
|
||||||
|
{
|
||||||
|
// this is kind of unnecessary because there's integration test coverage of Alert components
|
||||||
|
// but wanted to keep it anyway to see what's possible w.r.t. testing components
|
||||||
|
// in a unit test
|
||||||
|
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
prototypeManager.RegisterType(typeof(AlertPrototype));
|
||||||
|
var factory = IoCManager.Resolve<IComponentFactory>();
|
||||||
|
factory.Register<ServerAlertsComponent>();
|
||||||
|
prototypeManager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||||
|
prototypeManager.Resync();
|
||||||
|
var alertManager = IoCManager.Resolve<AlertManager>();
|
||||||
|
alertManager.Initialize();
|
||||||
|
|
||||||
|
|
||||||
|
var alertsComponent = new ServerAlertsComponent();
|
||||||
|
alertsComponent = IoCManager.InjectDependencies(alertsComponent);
|
||||||
|
|
||||||
|
Assert.That(alertManager.TryGetWithEncoded(AlertType.LowPressure, out var lowpressure, out var lpencoded));
|
||||||
|
Assert.That(alertManager.TryGetWithEncoded(AlertType.HighPressure, out var highpressure, out var hpencoded));
|
||||||
|
|
||||||
|
alertsComponent.ShowAlert(AlertType.LowPressure);
|
||||||
|
var alertState = alertsComponent.GetComponentState() as AlertsComponentState;
|
||||||
|
Assert.NotNull(alertState);
|
||||||
|
Assert.That(alertState.Alerts.Length, Is.EqualTo(1));
|
||||||
|
Assert.That(alertState.Alerts[0], Is.EqualTo(new AlertState{AlertEncoded = lpencoded}));
|
||||||
|
|
||||||
|
alertsComponent.ShowAlert(AlertType.HighPressure);
|
||||||
|
alertState = alertsComponent.GetComponentState() as AlertsComponentState;
|
||||||
|
Assert.That(alertState.Alerts.Length, Is.EqualTo(1));
|
||||||
|
Assert.That(alertState.Alerts[0], Is.EqualTo(new AlertState{AlertEncoded = hpencoded}));
|
||||||
|
|
||||||
|
alertsComponent.ClearAlertCategory(AlertCategory.Pressure);
|
||||||
|
alertState = alertsComponent.GetComponentState() as AlertsComponentState;
|
||||||
|
Assert.That(alertState.Alerts.Length, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Content.Tests/Shared/Alert/AlertManagerTests.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.Interfaces.Log;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.UnitTesting;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Tests.Shared.Alert
|
||||||
|
{
|
||||||
|
[TestFixture, TestOf(typeof(AlertManager))]
|
||||||
|
public class AlertManagerTests : RobustUnitTest
|
||||||
|
{
|
||||||
|
const string PROTOTYPES = @"
|
||||||
|
- type: alert
|
||||||
|
alertType: LowPressure
|
||||||
|
icon: /Textures/Interface/Alerts/Pressure/lowpressure.png
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: HighPressure
|
||||||
|
icon: /Textures/Interface/Alerts/Pressure/highpressure.png
|
||||||
|
";
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAlertManager()
|
||||||
|
{
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
prototypeManager.RegisterType(typeof(AlertPrototype));
|
||||||
|
prototypeManager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||||
|
IoCManager.RegisterInstance<AlertManager>(new AlertManager());
|
||||||
|
var alertManager = IoCManager.Resolve<AlertManager>();
|
||||||
|
alertManager.Initialize();
|
||||||
|
|
||||||
|
Assert.That(alertManager.TryGet(AlertType.LowPressure, out var lowPressure));
|
||||||
|
Assert.That(lowPressure.IconPath, Is.EqualTo("/Textures/Interface/Alerts/Pressure/lowpressure.png"));
|
||||||
|
Assert.That(alertManager.TryGet(AlertType.HighPressure, out var highPressure));
|
||||||
|
Assert.That(highPressure.IconPath, Is.EqualTo("/Textures/Interface/Alerts/Pressure/highpressure.png"));
|
||||||
|
|
||||||
|
Assert.That(alertManager.TryGetWithEncoded(AlertType.LowPressure, out lowPressure, out var encodedLowPressure));
|
||||||
|
Assert.That(lowPressure.IconPath, Is.EqualTo("/Textures/Interface/Alerts/Pressure/lowpressure.png"));
|
||||||
|
Assert.That(alertManager.TryGetWithEncoded(AlertType.HighPressure, out highPressure, out var encodedHighPressure));
|
||||||
|
Assert.That(highPressure.IconPath, Is.EqualTo("/Textures/Interface/Alerts/Pressure/highpressure.png"));
|
||||||
|
|
||||||
|
Assert.That(alertManager.TryEncode(lowPressure, out var encodedLowPressure2));
|
||||||
|
Assert.That(encodedLowPressure2, Is.EqualTo(encodedLowPressure));
|
||||||
|
Assert.That(alertManager.TryEncode(highPressure, out var encodedHighPressure2));
|
||||||
|
Assert.That(encodedHighPressure2, Is.EqualTo(encodedHighPressure));
|
||||||
|
Assert.That(encodedLowPressure, Is.Not.EqualTo(encodedHighPressure));
|
||||||
|
|
||||||
|
Assert.That(alertManager.TryDecode(encodedLowPressure, out var decodedLowPressure));
|
||||||
|
Assert.That(decodedLowPressure, Is.EqualTo(lowPressure));
|
||||||
|
Assert.That(alertManager.TryDecode(encodedHighPressure, out var decodedHighPressure));
|
||||||
|
Assert.That(decodedHighPressure, Is.EqualTo(highPressure));
|
||||||
|
|
||||||
|
Assert.False(alertManager.TryEncode(AlertType.Debug1, out _));
|
||||||
|
Assert.False(alertManager.TryGetWithEncoded(AlertType.Debug1, out _, out _));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.UnitTesting;
|
||||||
|
|
||||||
|
namespace Content.Tests.Shared.Alert
|
||||||
|
{
|
||||||
|
[TestFixture, TestOf(typeof(AlertOrderPrototype))]
|
||||||
|
public class AlertOrderPrototypeTests : RobustUnitTest
|
||||||
|
{
|
||||||
|
const string PROTOTYPES = @"
|
||||||
|
- type: alertOrder
|
||||||
|
order:
|
||||||
|
- alertType: Handcuffed
|
||||||
|
- category: Pressure
|
||||||
|
- category: Hunger
|
||||||
|
- alertType: Hot
|
||||||
|
- alertType: Stun
|
||||||
|
- alertType: LowPressure
|
||||||
|
- category: Temperature
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
category: Pressure
|
||||||
|
alertType: LowPressure
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
category: Hunger
|
||||||
|
alertType: Overfed
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
category: Pressure
|
||||||
|
alertType: HighPressure
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
category: Hunger
|
||||||
|
alertType: Peckish
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Stun
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Handcuffed
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
category: Temperature
|
||||||
|
alertType: Hot
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
category: Temperature
|
||||||
|
alertType: Cold
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Weightless
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: PilotingShuttle
|
||||||
|
";
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAlertOrderPrototype()
|
||||||
|
{
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
prototypeManager.RegisterType(typeof(AlertPrototype));
|
||||||
|
prototypeManager.RegisterType(typeof(AlertOrderPrototype));
|
||||||
|
prototypeManager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||||
|
|
||||||
|
var alertOrder = prototypeManager.EnumeratePrototypes<AlertOrderPrototype>().FirstOrDefault();
|
||||||
|
|
||||||
|
var alerts = prototypeManager.EnumeratePrototypes<AlertPrototype>();
|
||||||
|
|
||||||
|
// ensure they sort according to our expected criteria
|
||||||
|
var expectedOrder = new List<AlertType>();
|
||||||
|
expectedOrder.Add(AlertType.Handcuffed);
|
||||||
|
expectedOrder.Add(AlertType.HighPressure);
|
||||||
|
// stuff with only category + same category ordered by enum value
|
||||||
|
expectedOrder.Add(AlertType.Overfed);
|
||||||
|
expectedOrder.Add(AlertType.Peckish);
|
||||||
|
expectedOrder.Add(AlertType.Hot);
|
||||||
|
expectedOrder.Add(AlertType.Stun);
|
||||||
|
expectedOrder.Add(AlertType.LowPressure);
|
||||||
|
expectedOrder.Add(AlertType.Cold);
|
||||||
|
// stuff at end of list ordered by enum value
|
||||||
|
expectedOrder.Add(AlertType.Weightless);
|
||||||
|
expectedOrder.Add(AlertType.PilotingShuttle);
|
||||||
|
|
||||||
|
var actual = alerts.ToList();
|
||||||
|
actual.Sort(alertOrder);
|
||||||
|
|
||||||
|
Assert.That(actual.Select(a => a.AlertType).ToList(), Is.EqualTo(expectedOrder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Content.Tests/Shared/Alert/AlertPrototypeTests.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.Interfaces.Log;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.UnitTesting;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Tests.Shared.Alert
|
||||||
|
{
|
||||||
|
[TestFixture, TestOf(typeof(AlertPrototype))]
|
||||||
|
public class AlertPrototypeTests : RobustUnitTest
|
||||||
|
{
|
||||||
|
private const string PROTOTYPE = @"- type: alert
|
||||||
|
alertType: HumanHealth
|
||||||
|
category: Health
|
||||||
|
icon: /Textures/Interface/Alerts/Human/human.rsi/human.png
|
||||||
|
name: Health
|
||||||
|
description: ""[color=green]Green[/color] good. [color=red]Red[/color] bad.""
|
||||||
|
minSeverity: 0
|
||||||
|
maxSeverity: 6";
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAlertKey()
|
||||||
|
{
|
||||||
|
Assert.That(new AlertKey(AlertType.HumanHealth, null), Is.Not.EqualTo(AlertKey.ForCategory(AlertCategory.Health)));
|
||||||
|
Assert.That((new AlertKey(null, AlertCategory.Health)), Is.EqualTo(AlertKey.ForCategory(AlertCategory.Health)));
|
||||||
|
Assert.That((new AlertKey(AlertType.Buckled, AlertCategory.Health)), Is.EqualTo(AlertKey.ForCategory(AlertCategory.Health)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestCase(0, "/Textures/Interface/Alerts/Human/human.rsi/human0.png")]
|
||||||
|
[TestCase(null, "/Textures/Interface/Alerts/Human/human.rsi/human0.png")]
|
||||||
|
[TestCase(1, "/Textures/Interface/Alerts/Human/human.rsi/human1.png")]
|
||||||
|
[TestCase(6, "/Textures/Interface/Alerts/Human/human.rsi/human6.png")]
|
||||||
|
[TestCase(7, "/Textures/Interface/Alerts/Human/human.rsi/human6.png")]
|
||||||
|
public void GetsIconPath(short? severity, string expected)
|
||||||
|
{
|
||||||
|
|
||||||
|
var alert = GetTestPrototype();
|
||||||
|
Assert.That(alert.GetIconPath(severity), Is.EqualTo(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AlertPrototype GetTestPrototype()
|
||||||
|
{
|
||||||
|
using (TextReader stream = new StringReader(PROTOTYPE))
|
||||||
|
{
|
||||||
|
var yamlStream = new YamlStream();
|
||||||
|
yamlStream.Load(stream);
|
||||||
|
var document = yamlStream.Documents[0];
|
||||||
|
var rootNode = (YamlSequenceNode) document.RootNode;
|
||||||
|
var proto = (YamlMappingNode) rootNode[0];
|
||||||
|
var newReagent = new AlertPrototype();
|
||||||
|
newReagent.LoadFrom(proto);
|
||||||
|
return newReagent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -232,6 +232,8 @@
|
|||||||
- attachtogrid
|
- attachtogrid
|
||||||
- attachtograndparent
|
- attachtograndparent
|
||||||
- inrangeunoccluded
|
- inrangeunoccluded
|
||||||
|
- showalert
|
||||||
|
- clearalert
|
||||||
- hungry
|
- hungry
|
||||||
CanViewVar: true
|
CanViewVar: true
|
||||||
CanAdminPlace: true
|
CanAdminPlace: true
|
||||||
|
|||||||
205
Resources/Prototypes/Alerts/alerts.yml
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
- type: alertOrder
|
||||||
|
# Defines ordering in alert tab, higher up = higher in tab.
|
||||||
|
# List below can contain alert type or category, if both are present the id will take precedence.
|
||||||
|
# If item is not in list it will go at the bottom (ties broken by alert type enum value)
|
||||||
|
order:
|
||||||
|
- category: Health
|
||||||
|
- alertType: Fire
|
||||||
|
- alertType: Handcuffed
|
||||||
|
- category: Buckled
|
||||||
|
- alertType: Pulling
|
||||||
|
- category: Piloting
|
||||||
|
- alertType: Stun
|
||||||
|
- category: Pressure
|
||||||
|
- category: Temperature
|
||||||
|
- category: Hunger
|
||||||
|
- category: Thirst
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: LowPressure
|
||||||
|
category: Pressure
|
||||||
|
icon: /Textures/Interface/Alerts/Pressure/lowpressure.png
|
||||||
|
maxSeverity: 2
|
||||||
|
name: "[color=red]Low Pressure[/color]"
|
||||||
|
description: "The air around you is [color=red]hazardously thin[/color]. A [color=green]space suit[/color] would protect you."
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: HighPressure
|
||||||
|
category: Pressure
|
||||||
|
icon: /Textures/Interface/Alerts/Pressure/highpressure.png
|
||||||
|
maxSeverity: 2
|
||||||
|
name: "[color=red]High Pressure[/color]"
|
||||||
|
description: "The air around you is [color=red]hazardously thick[/color]. A [color=green]fire suit[/color] would protect you."
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Fire
|
||||||
|
icon: /Textures/Interface/Alerts/Fire/fire.png
|
||||||
|
name: "[color=red]On Fire[/color]"
|
||||||
|
description: "You're [color=red]on fire[/color]. Click the alert to stop, drop and roll to put the fire out or move to a vacuum area."
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Cold
|
||||||
|
category: Temperature
|
||||||
|
icon: /Textures/Interface/Alerts/Temperature/cold.png
|
||||||
|
maxSeverity: 3
|
||||||
|
name: "[color=cyan]Too Cold[/color]"
|
||||||
|
description: "You're [color=cyan]freezing cold![/color] Get somewhere warmer and take off any insulating clothing like a space suit."
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Hot
|
||||||
|
category: Temperature
|
||||||
|
icon: /Textures/Interface/Alerts/Temperature/hot.png
|
||||||
|
maxSeverity: 3
|
||||||
|
name: "[color=red]Too Hot[/color]"
|
||||||
|
description: "It's [color=red]too hot![/color] Flee to space or at least away from the flames. Standing on weeds will heal you."
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Weightless
|
||||||
|
icon: /Textures/Interface/Alerts/Weightless/weightless.png
|
||||||
|
name: Weightless
|
||||||
|
description: >-
|
||||||
|
Gravity has ceased affecting you, and you're floating around aimlessly. You'll need something large and heavy, like a
|
||||||
|
wall or lattice, to push yourself off if you want to move. A jetpack would enable free range of motion. A pair of
|
||||||
|
magboots would let you walk around normally on the floor. Barring those, you can throw things, use a fire extinguisher,
|
||||||
|
or shoot a gun to move around via Newton's 3rd Law of Motion.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Stun
|
||||||
|
icon: /Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png
|
||||||
|
name: "[color=yellow]Stunned[/color]"
|
||||||
|
description: "You're [color=yellow]stunned[/color]! Wait for it to wear off."
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Handcuffed
|
||||||
|
icon: /Textures/Interface/Alerts/Handcuffed/Handcuffed.png
|
||||||
|
name: "[color=yellow]Handcuffed[/color]"
|
||||||
|
description: "You're [color=yellow]handcuffed[/color] and can't act. If anyone drags you, you won't be able to move.."
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Buckled
|
||||||
|
category: Buckled
|
||||||
|
icon: /Textures/Interface/Alerts/Buckle/buckled.png
|
||||||
|
name: "[color=yellow]Buckled[/color]"
|
||||||
|
description: "You've been [color=yellow]buckled[/color] to something. Click the alert to unbuckle unless you're [color=yellow]handcuffed.[/color]"
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: HumanCrit
|
||||||
|
category: Health
|
||||||
|
icon: /Textures/Interface/Alerts/Human/humancrit-0.png
|
||||||
|
name: "[color=red]Critical Condition[/color]"
|
||||||
|
description: "You're severely injured and unconscious."
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: HumanDead
|
||||||
|
category: Health
|
||||||
|
icon: /Textures/Interface/Alerts/Human/humandead.png
|
||||||
|
name: Dead
|
||||||
|
description: You're dead.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: HumanHealth
|
||||||
|
category: Health
|
||||||
|
icon: /Textures/Interface/Alerts/Human/human.png
|
||||||
|
name: Health
|
||||||
|
description: "[color=green]Green[/color] good. [color=red]Red[/color] bad."
|
||||||
|
minSeverity: 0
|
||||||
|
maxSeverity: 6
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: PilotingShuttle
|
||||||
|
category: Piloting
|
||||||
|
icon: /Textures/Interface/Alerts/Buckle/buckled.png
|
||||||
|
name: Piloting Shuttle
|
||||||
|
description: You are piloting a shuttle. Click the alert to stop.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Overfed
|
||||||
|
category: Hunger
|
||||||
|
icon: /Textures/Interface/Alerts/Hunger/Overfed.png
|
||||||
|
name: "[color=yellow]Overfed[/color]"
|
||||||
|
description: You ate too much food, lardass. Run around the station and lose some weight.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Peckish
|
||||||
|
category: Hunger
|
||||||
|
icon: /Textures/Interface/Alerts/Hunger/Peckish.png
|
||||||
|
name: "[color=yellow]Peckish[/color]"
|
||||||
|
description: Some food would be good right about now.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Starving
|
||||||
|
category: Hunger
|
||||||
|
icon: /Textures/Interface/Alerts/Hunger/Starving.png
|
||||||
|
name: "[color=red]Starving[/color]"
|
||||||
|
description: You're severely malnourished. The hunger pains make moving around a chore.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Overhydrated
|
||||||
|
category: Thirst
|
||||||
|
icon: /Textures/Interface/Alerts/Thirst/OverHydrated.png
|
||||||
|
name: "[color=yellow]Overhydrated[/color]"
|
||||||
|
description: You drank too much.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Thirsty
|
||||||
|
category: Thirst
|
||||||
|
icon: /Textures/Interface/Alerts/Thirst/Thirsty.png
|
||||||
|
name: "[color=yellow]Thirsty[/color]"
|
||||||
|
description: Something to drink would be good right about now.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Parched
|
||||||
|
category: Thirst
|
||||||
|
icon: /Textures/Interface/Alerts/Thirst/Parched.png
|
||||||
|
name: "[color=red]Parched[/color]"
|
||||||
|
description: You're severely thirsty. The thirst makes moving around a chore.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Pulled
|
||||||
|
icon: /Textures/Interface/Alerts/Pull/pulled.png
|
||||||
|
name: Pulled
|
||||||
|
description: You're being pulled. Move to break free.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Pulling
|
||||||
|
icon: /Textures/Interface/Alerts/Pull/pulling.png
|
||||||
|
name: Pulling
|
||||||
|
description: You're pulling something. Click the alert to stop.
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Debug1
|
||||||
|
icon: /Textures/Interface/Alerts/Human/human1.png
|
||||||
|
name: Debug
|
||||||
|
description: Debug
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Debug2
|
||||||
|
icon: /Textures/Interface/Alerts/Human/human2.png
|
||||||
|
name: Debug
|
||||||
|
description: Debug
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Debug3
|
||||||
|
icon: /Textures/Interface/Alerts/Human/human3.png
|
||||||
|
name: Debug
|
||||||
|
description: Debug
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Debug4
|
||||||
|
icon: /Textures/Interface/Alerts/Human/human4.png
|
||||||
|
name: Debug
|
||||||
|
description: Debug
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Debug5
|
||||||
|
icon: /Textures/Interface/Alerts/Human/human5.png
|
||||||
|
name: Debug
|
||||||
|
description: Debug
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
alertType: Debug6
|
||||||
|
icon: /Textures/Interface/Alerts/Human/human6.png
|
||||||
|
name: Debug
|
||||||
|
description: Debug
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
show_examine_info: true
|
show_examine_info: true
|
||||||
- type: Input
|
- type: Input
|
||||||
context: "human"
|
context: "human"
|
||||||
- type: StatusEffectsUI
|
- type: AlertsUI
|
||||||
- type: OverlayEffectsUI
|
- type: OverlayEffectsUI
|
||||||
- type: Eye
|
- type: Eye
|
||||||
zoom: 0.5, 0.5
|
zoom: 0.5, 0.5
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 414 B After Width: | Height: | Size: 414 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 578 B |
|
Before Width: | Height: | Size: 601 B After Width: | Height: | Size: 601 B |
|
Before Width: | Height: | Size: 506 B After Width: | Height: | Size: 506 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 525 B After Width: | Height: | Size: 525 B |
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 396 B |
|
Before Width: | Height: | Size: 388 B After Width: | Height: | Size: 388 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 333 B After Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 786 B After Width: | Height: | Size: 786 B |
|
Before Width: | Height: | Size: 133 B After Width: | Height: | Size: 213 B |
|
After Width: | Height: | Size: 144 B |
BIN
Resources/Textures/Interface/Nano/window_background_bordered.png
Normal file
|
After Width: | Height: | Size: 213 B |