Merge remote-tracking branch 'upstream/master' into ed-08-09-2024-upstream
# Conflicts: # .github/PULL_REQUEST_TEMPLATE.md # Content.Server/Station/Systems/StationSpawningSystem.cs # Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml # Resources/Prototypes/Maps/box.yml # Resources/Prototypes/Maps/marathon.yml # Resources/Prototypes/Maps/meta.yml
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -13,7 +13,7 @@ public sealed partial class AdminMessagePopupMessage : Control
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Admin.SetMessage(FormattedMessage.FromMarkup(Loc.GetString(
|
||||
Admin.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString(
|
||||
"admin-notes-message-admin",
|
||||
("admin", message.AdminName),
|
||||
("date", message.AddedOn.ToLocalTime()))));
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed partial class AdminMessagePopupWindow : Control
|
||||
MessageContainer.AddChild(new AdminMessagePopupMessage(message));
|
||||
}
|
||||
|
||||
Description.SetMessage(FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
|
||||
Description.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
|
||||
}
|
||||
|
||||
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
Unread++;
|
||||
|
||||
var formatted = new FormattedMessage(1);
|
||||
formatted.AddMarkup($"[color=gray]{message.SentAt.ToShortTimeString()}[/color] {message.Text}");
|
||||
formatted.AddMarkupOrThrow($"[color=gray]{message.SentAt.ToShortTimeString()}[/color] {message.Text}");
|
||||
TextOutput.AddMessage(formatted);
|
||||
LastMessage = message.SentAt;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Gravity;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Anomaly.Ui;
|
||||
|
||||
81
Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml
Normal file
81
Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml
Normal file
@@ -0,0 +1,81 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="clr-namespace:Content.Client.Stylesheets"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Orientation="Vertical" HorizontalExpand ="True" Margin="0 0 0 3">
|
||||
|
||||
<!-- Device selection button -->
|
||||
<Button Name="FocusButton" HorizontalExpand="True" SetHeight="32" Margin="12 0 0 0" StyleClasses="OpenBoth" Access="Public">
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal">
|
||||
|
||||
<!-- Alarm state -->
|
||||
<TextureRect Stretch="Keep" HorizontalAlignment="Left" Margin="-20 -2 0 0" ModulateSelfOverride="#25252a" TexturePath="/Textures/Interface/AtmosMonitoring/status_bg.png">
|
||||
<BoxContainer VerticalExpand="True" HorizontalExpand="True" Orientation="Horizontal" Margin="8 0">
|
||||
<TextureRect Name="ArrowTexture" VerticalAlignment="Center" SetSize="12 12" Stretch="KeepAspectCentered" Margin="3 0" TexturePath="/Textures/Interface/Nano/triangle_right.png"></TextureRect>
|
||||
<Label Name="AlarmStateLabel" HorizontalExpand="True" HorizontalAlignment="Center" FontColorOverride="#5A5A5A" Text="{Loc 'atmos-alerts-window-invalid-state'}"></Label>
|
||||
</BoxContainer>
|
||||
</TextureRect>
|
||||
|
||||
<!-- Alarm name -->
|
||||
<Label Name="AlarmNameLabel" Text="???" HorizontalExpand="True" HorizontalAlignment="Center" Margin="5 0"></Label>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
|
||||
<!-- Panel that appears on selecting the device -->
|
||||
<PanelContainer Name="FocusContainer" HorizontalExpand="True" Margin="1 -1 1 0" ReservesSpace="False" Visible="False" Access="Public">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252a"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
|
||||
|
||||
<!-- Atmosphere status -->
|
||||
<Control>
|
||||
|
||||
<!-- Main container for displaying atmospheric data -->
|
||||
<BoxContainer Name="MainDataContainer" HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical" ReservesSpace="False" Visible="False">
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
||||
<Label Name="TemperatureHeaderLabel" Text="{Loc 'atmos-alerts-window-temperature-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
|
||||
<Label Name="PressureHeaderLabel" Text="{Loc 'atmos-alerts-window-pressure-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
|
||||
<Label Name="OxygenationHeaderLabel" Text="{Loc 'atmos-alerts-window-oxygenation-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
|
||||
</BoxContainer>
|
||||
<PanelContainer HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#202023"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
||||
<Label Name="TemperatureLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#5A5A5A" Margin="0 2 0 0" SetHeight="24"></Label>
|
||||
<Label Name="PressureLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#5A5A5A" Margin="0 2 0 0" SetHeight="24"></Label>
|
||||
<Label Name="OxygenationLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#5A5A5A" Margin="0 2 0 0" SetHeight="24"></Label>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
||||
<Label Name="GasesHeaderLabel" Text="{Loc 'atmos-alerts-window-other-gases-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 4 0 0" SetHeight="24"></Label>
|
||||
</BoxContainer>
|
||||
<PanelContainer HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#202023"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<!-- Gas entries added via C# code -->
|
||||
<GridContainer Name="GasGridContainer" HorizontalExpand="True" Columns = "4"></GridContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- If the alarm is inactive, this is label is diplayed instead -->
|
||||
<Label Name="NoDataLabel" Text="{Loc 'atmos-alerts-window-no-data-available'}" HorizontalAlignment="Center" Margin="0 15" FontColorOverride="#a9a9a9" ReservesSpace="False" Visible="False"></Label>
|
||||
|
||||
<!-- Silencing progress bar -->
|
||||
<controls:StripeBack Name="SilenceAlarmProgressBar" ReservesSpace="False" Visible="False" Access="Public">
|
||||
<PanelContainer>
|
||||
<Label Text="{Loc 'atmos-alerts-window-alerts-being-silenced'}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5 5 5 5"/>
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
</Control>
|
||||
|
||||
<!-- Check box for silencing this alarm -->
|
||||
<CheckBox Name="SilenceCheckBox" Text="{Loc 'atmos-alerts-window-silence-alerts'}" HorizontalAlignment="Left" Margin="5 5 5 5" Access="Public"></CheckBox>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
</BoxContainer>
|
||||
215
Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
Normal file
215
Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Atmos.Consoles;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AtmosAlarmEntryContainer : BoxContainer
|
||||
{
|
||||
public NetEntity NetEntity;
|
||||
public EntityCoordinates? Coordinates;
|
||||
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IResourceCache _cache;
|
||||
|
||||
private Dictionary<AtmosAlarmType, string> _alarmStrings = new Dictionary<AtmosAlarmType, string>()
|
||||
{
|
||||
[AtmosAlarmType.Invalid] = "atmos-alerts-window-invalid-state",
|
||||
[AtmosAlarmType.Normal] = "atmos-alerts-window-normal-state",
|
||||
[AtmosAlarmType.Warning] = "atmos-alerts-window-warning-state",
|
||||
[AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state",
|
||||
};
|
||||
|
||||
private Dictionary<Gas, string> _gasShorthands = new Dictionary<Gas, string>()
|
||||
{
|
||||
[Gas.Ammonia] = "NH₃",
|
||||
[Gas.CarbonDioxide] = "CO₂",
|
||||
[Gas.Frezon] = "F",
|
||||
[Gas.Nitrogen] = "N₂",
|
||||
[Gas.NitrousOxide] = "N₂O",
|
||||
[Gas.Oxygen] = "O₂",
|
||||
[Gas.Plasma] = "P",
|
||||
[Gas.Tritium] = "T",
|
||||
[Gas.WaterVapor] = "H₂O",
|
||||
};
|
||||
|
||||
public AtmosAlarmEntryContainer(NetEntity uid, EntityCoordinates? coordinates)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_cache = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
NetEntity = uid;
|
||||
Coordinates = coordinates;
|
||||
|
||||
// Load fonts
|
||||
var headerFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), 11);
|
||||
var normalFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11);
|
||||
var smallFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
|
||||
// Set fonts
|
||||
TemperatureHeaderLabel.FontOverride = headerFont;
|
||||
PressureHeaderLabel.FontOverride = headerFont;
|
||||
OxygenationHeaderLabel.FontOverride = headerFont;
|
||||
GasesHeaderLabel.FontOverride = headerFont;
|
||||
|
||||
TemperatureLabel.FontOverride = normalFont;
|
||||
PressureLabel.FontOverride = normalFont;
|
||||
OxygenationLabel.FontOverride = normalFont;
|
||||
|
||||
NoDataLabel.FontOverride = headerFont;
|
||||
|
||||
SilenceCheckBox.Label.FontOverride = smallFont;
|
||||
SilenceCheckBox.Label.FontColorOverride = Color.DarkGray;
|
||||
}
|
||||
|
||||
public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlertsFocusDeviceData? focusData = null)
|
||||
{
|
||||
NetEntity = entry.NetEntity;
|
||||
Coordinates = _entManager.GetCoordinates(entry.Coordinates);
|
||||
|
||||
// Load fonts
|
||||
var normalFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11);
|
||||
|
||||
// Update alarm state
|
||||
if (!_alarmStrings.TryGetValue(entry.AlarmState, out var alarmString))
|
||||
alarmString = "atmos-alerts-window-invalid-state";
|
||||
|
||||
AlarmStateLabel.Text = Loc.GetString(alarmString);
|
||||
AlarmStateLabel.FontColorOverride = GetAlarmStateColor(entry.AlarmState);
|
||||
|
||||
// Update alarm name
|
||||
AlarmNameLabel.Text = Loc.GetString("atmos-alerts-window-alarm-label", ("name", entry.EntityName), ("address", entry.Address));
|
||||
|
||||
// Focus updates
|
||||
FocusContainer.Visible = isFocus;
|
||||
|
||||
if (isFocus)
|
||||
SetAsFocus();
|
||||
else
|
||||
RemoveAsFocus();
|
||||
|
||||
if (isFocus && entry.Group == AtmosAlertsComputerGroup.AirAlarm)
|
||||
{
|
||||
MainDataContainer.Visible = (entry.AlarmState != AtmosAlarmType.Invalid);
|
||||
NoDataLabel.Visible = (entry.AlarmState == AtmosAlarmType.Invalid);
|
||||
|
||||
if (focusData != null)
|
||||
{
|
||||
// Update temperature
|
||||
var tempK = (FixedPoint2)focusData.Value.TemperatureData.Item1;
|
||||
var tempC = (FixedPoint2)TemperatureHelpers.KelvinToCelsius(tempK.Float());
|
||||
|
||||
TemperatureLabel.Text = Loc.GetString("atmos-alerts-window-temperature-value", ("valueInC", tempC), ("valueInK", tempK));
|
||||
TemperatureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.TemperatureData.Item2);
|
||||
|
||||
// Update pressure
|
||||
PressureLabel.Text = Loc.GetString("atmos-alerts-window-pressure-value", ("value", (FixedPoint2)focusData.Value.PressureData.Item1));
|
||||
PressureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.PressureData.Item2);
|
||||
|
||||
// Update oxygenation
|
||||
var oxygenPercent = (FixedPoint2)0f;
|
||||
var oxygenAlert = AtmosAlarmType.Invalid;
|
||||
|
||||
if (focusData.Value.GasData.TryGetValue(Gas.Oxygen, out var oxygenData))
|
||||
{
|
||||
oxygenPercent = oxygenData.Item2 * 100f;
|
||||
oxygenAlert = oxygenData.Item3;
|
||||
}
|
||||
|
||||
OxygenationLabel.Text = Loc.GetString("atmos-alerts-window-oxygenation-value", ("value", oxygenPercent));
|
||||
OxygenationLabel.FontColorOverride = GetAlarmStateColor(oxygenAlert);
|
||||
|
||||
// Update other present gases
|
||||
GasGridContainer.RemoveAllChildren();
|
||||
|
||||
var gasData = focusData.Value.GasData.Where(g => g.Key != Gas.Oxygen);
|
||||
|
||||
if (gasData.Count() == 0)
|
||||
{
|
||||
// No other gases
|
||||
var gasLabel = new Label()
|
||||
{
|
||||
Text = Loc.GetString("atmos-alerts-window-other-gases-value-nil"),
|
||||
FontOverride = normalFont,
|
||||
FontColorOverride = StyleNano.DisabledFore,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(0, 2, 0, 0),
|
||||
SetHeight = 24f,
|
||||
};
|
||||
|
||||
GasGridContainer.AddChild(gasLabel);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Add an entry for each gas
|
||||
foreach ((var gas, (var mol, var percent, var alert)) in gasData)
|
||||
{
|
||||
var gasPercent = (FixedPoint2)0f;
|
||||
gasPercent = percent * 100f;
|
||||
|
||||
if (!_gasShorthands.TryGetValue(gas, out var gasShorthand))
|
||||
gasShorthand = "X";
|
||||
|
||||
var gasLabel = new Label()
|
||||
{
|
||||
Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasShorthand), ("value", gasPercent)),
|
||||
FontOverride = normalFont,
|
||||
FontColorOverride = GetAlarmStateColor(alert),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(0, 2, 0, 0),
|
||||
SetHeight = 24f,
|
||||
};
|
||||
|
||||
GasGridContainer.AddChild(gasLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAsFocus()
|
||||
{
|
||||
FocusButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
ArrowTexture.TexturePath = "/Textures/Interface/Nano/inverted_triangle.svg.png";
|
||||
}
|
||||
|
||||
public void RemoveAsFocus()
|
||||
{
|
||||
FocusButton.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
ArrowTexture.TexturePath = "/Textures/Interface/Nano/triangle_right.png";
|
||||
FocusContainer.Visible = false;
|
||||
}
|
||||
|
||||
private Color GetAlarmStateColor(AtmosAlarmType alarmType)
|
||||
{
|
||||
switch (alarmType)
|
||||
{
|
||||
case AtmosAlarmType.Normal:
|
||||
return StyleNano.GoodGreenFore;
|
||||
case AtmosAlarmType.Warning:
|
||||
return StyleNano.ConcerningOrangeFore;
|
||||
case AtmosAlarmType.Danger:
|
||||
return StyleNano.DangerousRedFore;
|
||||
}
|
||||
|
||||
return StyleNano.DisabledFore;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Content.Shared.Atmos.Components;
|
||||
|
||||
namespace Content.Client.Atmos.Consoles;
|
||||
|
||||
public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private AtmosAlertsComputerWindow? _menu;
|
||||
|
||||
public AtmosAlertsComputerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
_menu = new AtmosAlertsComputerWindow(this, Owner);
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
|
||||
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
var castState = (AtmosAlertsComputerBoundInterfaceState) state;
|
||||
|
||||
if (castState == null)
|
||||
return;
|
||||
|
||||
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
|
||||
_menu?.UpdateUI(xform?.Coordinates, castState.AirAlarms, castState.FireAlarms, castState.FocusData);
|
||||
}
|
||||
|
||||
public void SendFocusChangeMessage(NetEntity? netEntity)
|
||||
{
|
||||
SendMessage(new AtmosAlertsComputerFocusChangeMessage(netEntity));
|
||||
}
|
||||
|
||||
public void SendDeviceSilencedMessage(NetEntity netEntity, bool silenceDevice)
|
||||
{
|
||||
SendMessage(new AtmosAlertsComputerDeviceSilencedMessage(netEntity, silenceDevice));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
108
Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
Normal file
108
Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
Normal file
@@ -0,0 +1,108 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'atmos-alerts-window-title'}"
|
||||
Resizable="False"
|
||||
SetSize="1120 750"
|
||||
MinSize="1120 750">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<!-- Main display -->
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
|
||||
<!-- Nav map -->
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
|
||||
<ui:NavMapControl Name="NavMap" Margin="5 5" VerticalExpand="True" HorizontalExpand="True">
|
||||
|
||||
<!-- System warning -->
|
||||
<PanelContainer Name="SystemWarningPanel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 48 0 0"
|
||||
Visible="False">
|
||||
<RichTextLabel Name="SystemWarningLabel" Margin="12 8 12 8"/>
|
||||
</PanelContainer>
|
||||
|
||||
</ui:NavMapControl>
|
||||
|
||||
<!-- Nav map legend -->
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 10 0 10">
|
||||
<Label Text="{Loc 'atmos-alerts-window-label-alert-types'}"
|
||||
Margin="20 0 5 0"/>
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
|
||||
Modulate="#5A5A5A"
|
||||
SetSize="16 16"
|
||||
Margin="20 0 5 0"/>
|
||||
<Label Text="{Loc 'atmos-alerts-window-invalid-state'}"/>
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
|
||||
Modulate="#32cd32"
|
||||
SetSize="16 16"
|
||||
Margin="20 0 5 0"/>
|
||||
<Label Text="{Loc 'atmos-alerts-window-normal-state'}"/>
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
|
||||
SetSize="16 16"
|
||||
Modulate="#ffb648"
|
||||
Margin="20 0 5 0"/>
|
||||
<Label Text="{Loc 'atmos-alerts-window-warning-state'}"/>
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_square.png"
|
||||
SetSize="16 16"
|
||||
Modulate="#ff4343"
|
||||
Margin="20 0 5 0"/>
|
||||
<Label Text="{Loc 'atmos-alerts-window-danger-state'}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Atmosphere status -->
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" SetWidth="440" Margin="0 0 10 10">
|
||||
|
||||
<!-- Station name -->
|
||||
<controls:StripeBack>
|
||||
<PanelContainer>
|
||||
<RichTextLabel Name="StationName" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 5 0 3"/>
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<!-- Alarm status (entries added by C# code) -->
|
||||
<TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True" Margin="0 10 0 0">
|
||||
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
|
||||
<BoxContainer Name="AlertsTable" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 0 10"/>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
|
||||
<BoxContainer Name="AirAlarmsTable" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 0 10"/>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
|
||||
<BoxContainer Name="FireAlarmsTable" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 0 10"/>
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
|
||||
<!-- Overlay toggles -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
|
||||
<Label Text="{Loc 'atmos-alerts-window-toggle-overlays'}" Margin="0 0 0 5"/>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<CheckBox Name="ShowInactiveAlarms" Text="{Loc 'atmos-alerts-window-invalid-state'}" Pressed="False" HorizontalExpand="True"/>
|
||||
<CheckBox Name="ShowNormalAlarms" Text="{Loc 'atmos-alerts-window-normal-state'}" Pressed="False" HorizontalExpand="True"/>
|
||||
<CheckBox Name="ShowWarningAlarms" Text="{Loc 'atmos-alerts-window-warning-state'}" Pressed="True" HorizontalExpand="True"/>
|
||||
<CheckBox Name="ShowDangerAlarms" Text="{Loc 'atmos-alerts-window-danger-state'}" Pressed="True" HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'atmos-alerts-window-flavor-left'}" StyleClasses="WindowFooterText" />
|
||||
<Label Text="{Loc 'atmos-alerts-window-flavor-right'}" StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
|
||||
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
548
Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs
Normal file
548
Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs
Normal file
@@ -0,0 +1,548 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Pinpointer.UI;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Atmos.Consoles;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AtmosAlertsComputerWindow : FancyWindow
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private EntityUid? _owner;
|
||||
private NetEntity? _trackedEntity;
|
||||
|
||||
private AtmosAlertsComputerEntry[]? _airAlarms = null;
|
||||
private AtmosAlertsComputerEntry[]? _fireAlarms = null;
|
||||
private IEnumerable<AtmosAlertsComputerEntry>? _allAlarms = null;
|
||||
|
||||
private IEnumerable<AtmosAlertsComputerEntry>? _activeAlarms = null;
|
||||
private Dictionary<NetEntity, float> _deviceSilencingProgress = new();
|
||||
|
||||
public event Action<NetEntity?>? SendFocusChangeMessageAction;
|
||||
public event Action<NetEntity, bool>? SendDeviceSilencedMessageAction;
|
||||
|
||||
private bool _autoScrollActive = false;
|
||||
private bool _autoScrollAwaitsUpdate = false;
|
||||
|
||||
private const float SilencingDuration = 2.5f;
|
||||
|
||||
public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
// Pass the owner to nav map
|
||||
_owner = owner;
|
||||
NavMap.Owner = _owner;
|
||||
|
||||
// Set nav map colors
|
||||
NavMap.WallColor = new Color(64, 64, 64);
|
||||
NavMap.TileColor = Color.DimGray * NavMap.WallColor;
|
||||
|
||||
// Set nav map grid uid
|
||||
var stationName = Loc.GetString("atmos-alerts-window-unknown-location");
|
||||
|
||||
if (_entManager.TryGetComponent<TransformComponent>(owner, out var xform))
|
||||
{
|
||||
NavMap.MapUid = xform.GridUid;
|
||||
|
||||
// Assign station name
|
||||
if (_entManager.TryGetComponent<MetaDataComponent>(xform.GridUid, out var stationMetaData))
|
||||
stationName = stationMetaData.EntityName;
|
||||
|
||||
var msg = new FormattedMessage();
|
||||
msg.TryAddMarkup(Loc.GetString("atmos-alerts-window-station-name", ("stationName", stationName)), out _);
|
||||
|
||||
StationName.SetMessage(msg);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
StationName.SetMessage(stationName);
|
||||
NavMap.Visible = false;
|
||||
}
|
||||
|
||||
// Set trackable entity selected action
|
||||
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
|
||||
|
||||
// Update nav map
|
||||
NavMap.ForceNavMapUpdate();
|
||||
|
||||
// Set tab container headers
|
||||
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts"));
|
||||
MasterTabContainer.SetTabTitle(1, Loc.GetString("atmos-alerts-window-tab-air-alarms"));
|
||||
MasterTabContainer.SetTabTitle(2, Loc.GetString("atmos-alerts-window-tab-fire-alarms"));
|
||||
|
||||
// Set UI toggles
|
||||
ShowInactiveAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowInactiveAlarms, AtmosAlarmType.Invalid);
|
||||
ShowNormalAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowNormalAlarms, AtmosAlarmType.Normal);
|
||||
ShowWarningAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowWarningAlarms, AtmosAlarmType.Warning);
|
||||
ShowDangerAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowDangerAlarms, AtmosAlarmType.Danger);
|
||||
|
||||
// Set atmos monitoring message action
|
||||
SendFocusChangeMessageAction += userInterface.SendFocusChangeMessage;
|
||||
SendDeviceSilencedMessageAction += userInterface.SendDeviceSilencedMessage;
|
||||
}
|
||||
|
||||
#region Toggle handling
|
||||
|
||||
private void OnShowAlarmsToggled(CheckBox toggle, AtmosAlarmType toggledAlarmState)
|
||||
{
|
||||
if (_owner == null)
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner.Value, out var console))
|
||||
return;
|
||||
|
||||
foreach (var device in console.AtmosDevices)
|
||||
{
|
||||
var alarmState = GetAlarmState(device.NetEntity);
|
||||
|
||||
if (toggledAlarmState != alarmState)
|
||||
continue;
|
||||
|
||||
if (toggle.Pressed)
|
||||
AddTrackedEntityToNavMap(device, alarmState);
|
||||
|
||||
else
|
||||
NavMap.TrackedEntities.Remove(device.NetEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSilenceAlertsToggled(NetEntity netEntity, bool toggleState)
|
||||
{
|
||||
if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner, out var console))
|
||||
return;
|
||||
|
||||
if (toggleState)
|
||||
_deviceSilencingProgress[netEntity] = SilencingDuration;
|
||||
|
||||
else
|
||||
_deviceSilencingProgress.Remove(netEntity);
|
||||
|
||||
foreach (AtmosAlarmEntryContainer entryContainer in AlertsTable.Children)
|
||||
{
|
||||
if (entryContainer.NetEntity == netEntity)
|
||||
entryContainer.SilenceAlarmProgressBar.Visible = toggleState;
|
||||
}
|
||||
|
||||
SendDeviceSilencedMessageAction?.Invoke(netEntity, toggleState);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void UpdateUI(EntityCoordinates? consoleCoords, AtmosAlertsComputerEntry[] airAlarms, AtmosAlertsComputerEntry[] fireAlarms, AtmosAlertsFocusDeviceData? focusData)
|
||||
{
|
||||
if (_owner == null)
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner.Value, out var console))
|
||||
return;
|
||||
|
||||
if (_trackedEntity != focusData?.NetEntity)
|
||||
{
|
||||
SendFocusChangeMessageAction?.Invoke(_trackedEntity);
|
||||
focusData = null;
|
||||
}
|
||||
|
||||
// Retain alarm data for use inbetween updates
|
||||
_airAlarms = airAlarms;
|
||||
_fireAlarms = fireAlarms;
|
||||
_allAlarms = airAlarms.Concat(fireAlarms);
|
||||
|
||||
var silenced = console.SilencedDevices;
|
||||
|
||||
_activeAlarms = _allAlarms.Where(x => x.AlarmState > AtmosAlarmType.Normal &&
|
||||
(!silenced.Contains(x.NetEntity) || _deviceSilencingProgress.ContainsKey(x.NetEntity)));
|
||||
|
||||
// Reset nav map data
|
||||
NavMap.TrackedCoordinates.Clear();
|
||||
NavMap.TrackedEntities.Clear();
|
||||
|
||||
// Add tracked entities to the nav map
|
||||
foreach (var device in console.AtmosDevices)
|
||||
{
|
||||
if (!NavMap.Visible)
|
||||
continue;
|
||||
|
||||
var alarmState = GetAlarmState(device.NetEntity);
|
||||
|
||||
if (_trackedEntity != device.NetEntity)
|
||||
{
|
||||
// Skip air alarms if the appropriate overlay is off
|
||||
if (!ShowInactiveAlarms.Pressed && alarmState == AtmosAlarmType.Invalid)
|
||||
continue;
|
||||
|
||||
if (!ShowNormalAlarms.Pressed && alarmState == AtmosAlarmType.Normal)
|
||||
continue;
|
||||
|
||||
if (!ShowWarningAlarms.Pressed && alarmState == AtmosAlarmType.Warning)
|
||||
continue;
|
||||
|
||||
if (!ShowDangerAlarms.Pressed && alarmState == AtmosAlarmType.Danger)
|
||||
continue;
|
||||
}
|
||||
|
||||
AddTrackedEntityToNavMap(device, alarmState);
|
||||
}
|
||||
|
||||
// Show the monitor location
|
||||
var consoleUid = _entManager.GetNetEntity(_owner);
|
||||
|
||||
if (consoleCoords != null && consoleUid != null)
|
||||
{
|
||||
var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
|
||||
var blip = new NavMapBlip(consoleCoords.Value, texture, Color.Cyan, true, false);
|
||||
NavMap.TrackedEntities[consoleUid.Value] = blip;
|
||||
}
|
||||
|
||||
// Update the nav map
|
||||
NavMap.ForceNavMapUpdate();
|
||||
|
||||
// Clear excess children from the tables
|
||||
var activeAlarmCount = _activeAlarms.Count();
|
||||
|
||||
while (AlertsTable.ChildCount > activeAlarmCount)
|
||||
AlertsTable.RemoveChild(AlertsTable.GetChild(AlertsTable.ChildCount - 1));
|
||||
|
||||
while (AirAlarmsTable.ChildCount > airAlarms.Length)
|
||||
AirAlarmsTable.RemoveChild(AirAlarmsTable.GetChild(AirAlarmsTable.ChildCount - 1));
|
||||
|
||||
while (FireAlarmsTable.ChildCount > fireAlarms.Length)
|
||||
FireAlarmsTable.RemoveChild(FireAlarmsTable.GetChild(FireAlarmsTable.ChildCount - 1));
|
||||
|
||||
// Update all entries in each table
|
||||
for (int index = 0; index < _activeAlarms.Count(); index++)
|
||||
{
|
||||
var entry = _activeAlarms.ElementAt(index);
|
||||
UpdateUIEntry(entry, index, AlertsTable, console, focusData);
|
||||
}
|
||||
|
||||
for (int index = 0; index < airAlarms.Count(); index++)
|
||||
{
|
||||
var entry = airAlarms.ElementAt(index);
|
||||
UpdateUIEntry(entry, index, AirAlarmsTable, console, focusData);
|
||||
}
|
||||
|
||||
for (int index = 0; index < fireAlarms.Count(); index++)
|
||||
{
|
||||
var entry = fireAlarms.ElementAt(index);
|
||||
UpdateUIEntry(entry, index, FireAlarmsTable, console, focusData);
|
||||
}
|
||||
|
||||
// If no alerts are active, display a message
|
||||
if (MasterTabContainer.CurrentTab == 0 && activeAlarmCount == 0)
|
||||
{
|
||||
var label = new RichTextLabel()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
};
|
||||
|
||||
label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", StyleNano.GoodGreenFore.ToHexNoAlpha())));
|
||||
|
||||
AlertsTable.AddChild(label);
|
||||
}
|
||||
|
||||
// Update the alerts tab with the number of active alerts
|
||||
if (activeAlarmCount == 0)
|
||||
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts"));
|
||||
|
||||
else
|
||||
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount)));
|
||||
|
||||
// Auto-scroll re-enable
|
||||
if (_autoScrollAwaitsUpdate)
|
||||
{
|
||||
_autoScrollActive = true;
|
||||
_autoScrollAwaitsUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTrackedEntityToNavMap(AtmosAlertsDeviceNavMapData metaData, AtmosAlarmType alarmState)
|
||||
{
|
||||
var data = GetBlipTexture(alarmState);
|
||||
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var texture = data.Value.Item1;
|
||||
var color = data.Value.Item2;
|
||||
var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
|
||||
|
||||
if (_trackedEntity != null && _trackedEntity != metaData.NetEntity)
|
||||
color *= Color.DimGray;
|
||||
|
||||
var selectable = true;
|
||||
var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable);
|
||||
|
||||
NavMap.TrackedEntities[metaData.NetEntity] = blip;
|
||||
}
|
||||
|
||||
private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null)
|
||||
{
|
||||
// Make new UI entry if required
|
||||
if (index >= table.ChildCount)
|
||||
{
|
||||
var newEntryContainer = new AtmosAlarmEntryContainer(entry.NetEntity, _entManager.GetCoordinates(entry.Coordinates));
|
||||
|
||||
// On click
|
||||
newEntryContainer.FocusButton.OnButtonUp += args =>
|
||||
{
|
||||
if (_trackedEntity == newEntryContainer.NetEntity)
|
||||
{
|
||||
_trackedEntity = null;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_trackedEntity = newEntryContainer.NetEntity;
|
||||
|
||||
if (newEntryContainer.Coordinates != null)
|
||||
NavMap.CenterToCoordinates(newEntryContainer.Coordinates.Value);
|
||||
}
|
||||
|
||||
// Send message to console that the focus has changed
|
||||
SendFocusChangeMessageAction?.Invoke(_trackedEntity);
|
||||
|
||||
// Update affected UI elements across all tables
|
||||
UpdateConsoleTable(console, AlertsTable, _trackedEntity);
|
||||
UpdateConsoleTable(console, AirAlarmsTable, _trackedEntity);
|
||||
UpdateConsoleTable(console, FireAlarmsTable, _trackedEntity);
|
||||
};
|
||||
|
||||
// On toggling the silence check box
|
||||
newEntryContainer.SilenceCheckBox.OnToggled += _ => OnSilenceAlertsToggled(newEntryContainer.NetEntity, newEntryContainer.SilenceCheckBox.Pressed);
|
||||
|
||||
// Add the entry to the current table
|
||||
table.AddChild(newEntryContainer);
|
||||
}
|
||||
|
||||
// Update values and UI elements
|
||||
var tableChild = table.GetChild(index);
|
||||
|
||||
if (tableChild is not AtmosAlarmEntryContainer)
|
||||
{
|
||||
table.RemoveChild(tableChild);
|
||||
UpdateUIEntry(entry, index, table, console, focusData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var entryContainer = (AtmosAlarmEntryContainer)tableChild;
|
||||
|
||||
entryContainer.UpdateEntry(entry, entry.NetEntity == _trackedEntity, focusData);
|
||||
|
||||
if (_trackedEntity != entry.NetEntity)
|
||||
{
|
||||
var silenced = console.SilencedDevices;
|
||||
entryContainer.SilenceCheckBox.Pressed = (silenced.Contains(entry.NetEntity) || _deviceSilencingProgress.ContainsKey(entry.NetEntity));
|
||||
}
|
||||
|
||||
entryContainer.SilenceAlarmProgressBar.Visible = (table == AlertsTable && _deviceSilencingProgress.ContainsKey(entry.NetEntity));
|
||||
}
|
||||
|
||||
private void UpdateConsoleTable(AtmosAlertsComputerComponent console, Control table, NetEntity? currTrackedEntity)
|
||||
{
|
||||
foreach (var tableChild in table.Children)
|
||||
{
|
||||
if (tableChild is not AtmosAlarmEntryContainer)
|
||||
continue;
|
||||
|
||||
var entryContainer = (AtmosAlarmEntryContainer)tableChild;
|
||||
|
||||
if (entryContainer.NetEntity != currTrackedEntity)
|
||||
entryContainer.RemoveAsFocus();
|
||||
|
||||
else if (entryContainer.NetEntity == currTrackedEntity)
|
||||
entryContainer.SetAsFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTrackedEntityFromNavMap(NetEntity? netEntity)
|
||||
{
|
||||
if (netEntity == null)
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner, out var console))
|
||||
return;
|
||||
|
||||
_trackedEntity = netEntity;
|
||||
|
||||
if (netEntity != null)
|
||||
{
|
||||
// Tab switching
|
||||
if (MasterTabContainer.CurrentTab != 0 || _activeAlarms?.Any(x => x.NetEntity == netEntity) == false)
|
||||
{
|
||||
var device = console.AtmosDevices.FirstOrNull(x => x.NetEntity == netEntity);
|
||||
|
||||
switch (device?.Group)
|
||||
{
|
||||
case AtmosAlertsComputerGroup.AirAlarm:
|
||||
MasterTabContainer.CurrentTab = 1; break;
|
||||
case AtmosAlertsComputerGroup.FireAlarm:
|
||||
MasterTabContainer.CurrentTab = 2; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the scroll position of the selected entity on the selected button the UI
|
||||
ActivateAutoScrollToFocus();
|
||||
}
|
||||
|
||||
// Send message to console that the focus has changed
|
||||
SendFocusChangeMessageAction?.Invoke(_trackedEntity);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
AutoScrollToFocus();
|
||||
|
||||
// Device silencing update
|
||||
foreach ((var device, var remainingTime) in _deviceSilencingProgress)
|
||||
{
|
||||
var t = remainingTime - args.DeltaSeconds;
|
||||
|
||||
if (t <= 0)
|
||||
{
|
||||
_deviceSilencingProgress.Remove(device);
|
||||
|
||||
if (device == _trackedEntity)
|
||||
_trackedEntity = null;
|
||||
}
|
||||
|
||||
else
|
||||
_deviceSilencingProgress[device] = t;
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateAutoScrollToFocus()
|
||||
{
|
||||
_autoScrollActive = false;
|
||||
_autoScrollAwaitsUpdate = true;
|
||||
}
|
||||
|
||||
private void AutoScrollToFocus()
|
||||
{
|
||||
if (!_autoScrollActive)
|
||||
return;
|
||||
|
||||
var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
|
||||
if (scroll == null)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
return;
|
||||
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
_autoScrollActive = false;
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var child in scroll.Children)
|
||||
{
|
||||
if (child is not VScrollBar)
|
||||
continue;
|
||||
|
||||
var castChild = child as VScrollBar;
|
||||
|
||||
if (castChild != null)
|
||||
{
|
||||
vScrollBar = castChild;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = null;
|
||||
|
||||
var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
|
||||
if (scroll == null)
|
||||
return false;
|
||||
|
||||
var container = scroll.Children.ElementAt(0) as BoxContainer;
|
||||
if (container == null || container.Children.Count() == 0)
|
||||
return false;
|
||||
|
||||
// Exit if the heights of the children haven't been initialized yet
|
||||
if (!container.Children.Any(x => x.Height > 0))
|
||||
return false;
|
||||
|
||||
nextScrollPosition = 0;
|
||||
|
||||
foreach (var control in container.Children)
|
||||
{
|
||||
if (control == null || control is not AtmosAlarmEntryContainer)
|
||||
continue;
|
||||
|
||||
if (((AtmosAlarmEntryContainer)control).NetEntity == _trackedEntity)
|
||||
return true;
|
||||
|
||||
nextScrollPosition += control.Height;
|
||||
}
|
||||
|
||||
// Failed to find control
|
||||
nextScrollPosition = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private AtmosAlarmType GetAlarmState(NetEntity netEntity)
|
||||
{
|
||||
var alarmState = _allAlarms?.FirstOrNull(x => x.NetEntity == netEntity)?.AlarmState;
|
||||
|
||||
if (alarmState == null)
|
||||
return AtmosAlarmType.Invalid;
|
||||
|
||||
return alarmState.Value;
|
||||
}
|
||||
|
||||
private (SpriteSpecifier.Texture, Color)? GetBlipTexture(AtmosAlarmType alarmState)
|
||||
{
|
||||
(SpriteSpecifier.Texture, Color)? output = null;
|
||||
|
||||
switch (alarmState)
|
||||
{
|
||||
case AtmosAlarmType.Invalid:
|
||||
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), StyleNano.DisabledFore); break;
|
||||
case AtmosAlarmType.Normal:
|
||||
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), Color.LimeGreen); break;
|
||||
case AtmosAlarmType.Warning:
|
||||
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), new Color(255, 182, 72)); break;
|
||||
case AtmosAlarmType.Danger:
|
||||
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), new Color(255, 67, 67)); break;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -131,13 +131,13 @@ public sealed partial class ChangelogTab : Control
|
||||
Margin = new Thickness(6, 0, 0, 0),
|
||||
};
|
||||
authorLabel.SetMessage(
|
||||
FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author))));
|
||||
FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", author))));
|
||||
ChangelogBody.AddChild(authorLabel);
|
||||
|
||||
foreach (var change in groupedEntry.SelectMany(c => c.Changes))
|
||||
{
|
||||
var text = new RichTextLabel();
|
||||
text.SetMessage(FormattedMessage.FromMarkup(change.Message));
|
||||
text.SetMessage(FormattedMessage.FromMarkupOrThrow(change.Message));
|
||||
ChangelogBody.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace Content.Client.Chat.UI
|
||||
var msg = new FormattedMessage();
|
||||
if (fontColor != null)
|
||||
msg.PushColor(fontColor.Value);
|
||||
msg.AddMarkup(message);
|
||||
msg.AddMarkupOrThrow(message);
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Chemistry;
|
||||
@@ -16,7 +16,7 @@ namespace Content.Client.Chemistry.EntitySystems;
|
||||
/// <inheritdoc/>
|
||||
public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
{
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
[ValidatePrototypeId<MixingCategoryPrototype>]
|
||||
private const string DefaultMixingCategory = "DummyMix";
|
||||
|
||||
@@ -57,7 +57,6 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
};
|
||||
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
|
||||
public override string Command => "mappingclientsidesetup";
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ namespace Content.Client.Computer
|
||||
[Virtual]
|
||||
public class ComputerBoundUserInterface<TWindow, TState> : ComputerBoundUserInterfaceBase where TWindow : BaseWindow, IComputerWindow<TState>, new() where TState : BoundUserInterfaceState
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private TWindow? _window;
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ namespace Content.Client.Credits
|
||||
var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
|
||||
if (markup)
|
||||
{
|
||||
label.SetMessage(FormattedMessage.FromMarkup(text.Trim()));
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -227,7 +227,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
StatusOptionButton.SelectId((int) criminalRecord.Status);
|
||||
if (criminalRecord.Reason is {} reason)
|
||||
{
|
||||
var message = FormattedMessage.FromMarkup(Loc.GetString("criminal-records-console-wanted-reason"));
|
||||
var message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-wanted-reason"));
|
||||
message.AddText($": {reason}");
|
||||
WantedReason.SetMessage(message);
|
||||
WantedReason.Visible = true;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Gravity;
|
||||
@@ -21,7 +22,7 @@ public sealed partial class GravitySystem : SharedGravitySystem
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (_appearanceSystem.TryGetData<GravityGeneratorStatus>(uid, GravityGeneratorVisuals.State, out var state, args.Component))
|
||||
if (_appearanceSystem.TryGetData<PowerChargeStatus>(uid, PowerChargeVisuals.State, out var state, args.Component))
|
||||
{
|
||||
if (comp.SpriteMap.TryGetValue(state, out var spriteState))
|
||||
{
|
||||
@@ -30,7 +31,7 @@ public sealed partial class GravitySystem : SharedGravitySystem
|
||||
}
|
||||
}
|
||||
|
||||
if (_appearanceSystem.TryGetData<float>(uid, GravityGeneratorVisuals.Charge, out var charge, args.Component))
|
||||
if (_appearanceSystem.TryGetData<float>(uid, PowerChargeVisuals.Charge, out var charge, args.Component))
|
||||
{
|
||||
var layer = args.Sprite.LayerMapGet(GravityGeneratorVisualLayers.Core);
|
||||
switch (charge)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
using Content.Shared.Gravity;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Gravity.UI
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GravityGeneratorBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private GravityGeneratorWindow? _window;
|
||||
|
||||
public GravityGeneratorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<GravityGeneratorWindow>();
|
||||
_window.SetEntity(Owner);
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
var castState = (SharedGravityGeneratorComponent.GeneratorState) state;
|
||||
_window?.UpdateState(castState);
|
||||
}
|
||||
|
||||
public void SetPowerSwitch(bool on)
|
||||
{
|
||||
SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(on));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using Content.Shared.Gravity;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||
|
||||
namespace Content.Client.Gravity.UI
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GravityGeneratorWindow : FancyWindow
|
||||
{
|
||||
private readonly ButtonGroup _buttonGroup = new();
|
||||
|
||||
public event Action<bool>? OnPowerSwitch;
|
||||
|
||||
public GravityGeneratorWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
OnButton.Group = _buttonGroup;
|
||||
OffButton.Group = _buttonGroup;
|
||||
|
||||
OnButton.OnPressed += _ => OnPowerSwitch?.Invoke(true);
|
||||
OffButton.OnPressed += _ => OnPowerSwitch?.Invoke(false);
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid uid)
|
||||
{
|
||||
EntityView.SetEntity(uid);
|
||||
}
|
||||
|
||||
public void UpdateState(SharedGravityGeneratorComponent.GeneratorState state)
|
||||
{
|
||||
if (state.On)
|
||||
OnButton.Pressed = true;
|
||||
else
|
||||
OffButton.Pressed = true;
|
||||
|
||||
PowerLabel.Text = Loc.GetString(
|
||||
"gravity-generator-window-power-label",
|
||||
("draw", state.PowerDraw),
|
||||
("max", state.PowerDrawMax));
|
||||
|
||||
PowerLabel.SetOnlyStyleClass(MathHelper.CloseTo(state.PowerDraw, state.PowerDrawMax) ? "Good" : "Caution");
|
||||
|
||||
ChargeBar.Value = state.Charge;
|
||||
ChargeText.Text = (state.Charge / 255f).ToString("P0");
|
||||
StatusLabel.Text = Loc.GetString(state.PowerStatus switch
|
||||
{
|
||||
GravityGeneratorPowerStatus.Off => "gravity-generator-window-status-off",
|
||||
GravityGeneratorPowerStatus.Discharging => "gravity-generator-window-status-discharging",
|
||||
GravityGeneratorPowerStatus.Charging => "gravity-generator-window-status-charging",
|
||||
GravityGeneratorPowerStatus.FullyCharged => "gravity-generator-window-status-fully-charged",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
});
|
||||
|
||||
StatusLabel.SetOnlyStyleClass(state.PowerStatus switch
|
||||
{
|
||||
GravityGeneratorPowerStatus.Off => "Danger",
|
||||
GravityGeneratorPowerStatus.Discharging => "Caution",
|
||||
GravityGeneratorPowerStatus.Charging => "Caution",
|
||||
GravityGeneratorPowerStatus.FullyCharged => "Good",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
});
|
||||
|
||||
EtaLabel.Text = state.EtaSeconds >= 0
|
||||
? Loc.GetString("gravity-generator-window-eta-value", ("left", TimeSpan.FromSeconds(state.EtaSeconds)))
|
||||
: Loc.GetString("gravity-generator-window-eta-none");
|
||||
|
||||
EtaLabel.SetOnlyStyleClass(state.EtaSeconds >= 0 ? "Caution" : "Disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
||||
var i = 0;
|
||||
foreach (var effectString in effect.EffectDescriptions)
|
||||
{
|
||||
descMsg.AddMarkup(effectString);
|
||||
descMsg.AddMarkupOrThrow(effectString);
|
||||
i++;
|
||||
if (i < descriptionsCount)
|
||||
descMsg.PushNewline();
|
||||
@@ -174,7 +174,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
||||
var i = 0;
|
||||
foreach (var effectString in guideEntryRegistryPlant.PlantMetabolisms)
|
||||
{
|
||||
descMsg.AddMarkup(effectString);
|
||||
descMsg.AddMarkupOrThrow(effectString);
|
||||
i++;
|
||||
if (i < descriptionsCount)
|
||||
descMsg.PushNewline();
|
||||
@@ -195,7 +195,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
||||
FormattedMessage description = new();
|
||||
description.AddText(reagent.LocalizedDescription);
|
||||
description.PushNewline();
|
||||
description.AddMarkup(Loc.GetString("guidebook-reagent-physical-description",
|
||||
description.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-physical-description",
|
||||
("description", reagent.LocalizedPhysicalDescription)));
|
||||
ReagentDescription.SetMessage(description);
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public sealed partial class GuideReagentReaction : BoxContainer, ISearchableCont
|
||||
var i = 0;
|
||||
foreach (var (product, amount) in reagents.OrderByDescending(p => p.Value))
|
||||
{
|
||||
msg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
|
||||
msg.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-recipes-reagent-display",
|
||||
("reagent", protoMan.Index<ReagentPrototype>(product).LocalizedName), ("ratio", amount)));
|
||||
i++;
|
||||
if (i < reagentCount)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -18,7 +18,7 @@ public sealed partial class InfoSection : BoxContainer
|
||||
{
|
||||
TitleLabel.Text = title;
|
||||
if (markup)
|
||||
Content.SetMessage(FormattedMessage.FromMarkup(text.Trim()));
|
||||
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
|
||||
else
|
||||
Content.SetMessage(text);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Client.Info
|
||||
}
|
||||
public void SetInfoBlob(string markup)
|
||||
{
|
||||
_richTextLabel.SetMessage(FormattedMessage.FromMarkup(markup));
|
||||
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace Content.Client.Labels.UI
|
||||
|
||||
_window = this.CreateWindow<HandLabelerWindow>();
|
||||
|
||||
if (_entManager.TryGetComponent(Owner, out HandLabelerComponent? labeler))
|
||||
{
|
||||
_window.SetMaxLabelLength(labeler!.MaxLabelChars);
|
||||
}
|
||||
|
||||
_window.OnLabelChanged += OnLabelChanged;
|
||||
Reload();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Content.Client.Labels.UI
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
LabelLineEdit.OnTextEntered += e =>
|
||||
LabelLineEdit.OnTextChanged += e =>
|
||||
{
|
||||
_label = e.Text;
|
||||
OnLabelChanged?.Invoke(_label);
|
||||
@@ -33,6 +33,10 @@ namespace Content.Client.Labels.UI
|
||||
_focused = false;
|
||||
LabelLineEdit.Text = _label;
|
||||
};
|
||||
|
||||
// Give the editor keybard focus, since that's the only
|
||||
// thing the user will want to be doing with this UI
|
||||
LabelLineEdit.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
public void SetCurrentLabel(string label)
|
||||
@@ -44,5 +48,10 @@ namespace Content.Client.Labels.UI
|
||||
if (!_focused)
|
||||
LabelLineEdit.Text = label;
|
||||
}
|
||||
|
||||
public void SetMaxLabelLength(int maxLength)
|
||||
{
|
||||
LabelLineEdit.IsValid = s => s.Length <= maxLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,9 +359,6 @@ namespace Content.Client.Light.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class LightBehaviourComponent : SharedLightBehaviourComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public const string KeyPrefix = nameof(LightBehaviourComponent);
|
||||
|
||||
public sealed class AnimationContainer
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace Content.Client.MachineLinking.UI;
|
||||
|
||||
public sealed class SignalTimerBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private SignalTimerWindow? _window;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public static class RichTextLabelExt
|
||||
/// </remarks>
|
||||
public static RichTextLabel SetMarkup(this RichTextLabel label, string markup)
|
||||
{
|
||||
label.SetMessage(FormattedMessage.FromMarkup(markup));
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
96
Content.Client/Mining/MiningOverlay.cs
Normal file
96
Content.Client/Mining/MiningOverlay.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Mining.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Mining;
|
||||
|
||||
public sealed class MiningOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SpriteSystem _sprite;
|
||||
private readonly TransformSystem _xform;
|
||||
|
||||
private readonly EntityQuery<SpriteComponent> _spriteQuery;
|
||||
private readonly EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override bool RequestScreenTexture => false;
|
||||
|
||||
private readonly HashSet<Entity<MiningScannerViewableComponent>> _viewableEnts = new();
|
||||
|
||||
public MiningOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_lookup = _entityManager.System<EntityLookupSystem>();
|
||||
_sprite = _entityManager.System<SpriteSystem>();
|
||||
_xform = _entityManager.System<TransformSystem>();
|
||||
|
||||
_spriteQuery = _entityManager.GetEntityQuery<SpriteComponent>();
|
||||
_xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
|
||||
if (_player.LocalEntity is not { } localEntity ||
|
||||
!_entityManager.TryGetComponent<MiningScannerViewerComponent>(localEntity, out var viewerComp))
|
||||
return;
|
||||
|
||||
if (viewerComp.LastPingLocation == null)
|
||||
return;
|
||||
|
||||
var scaleMatrix = Matrix3Helpers.CreateScale(Vector2.One);
|
||||
|
||||
_viewableEnts.Clear();
|
||||
_lookup.GetEntitiesInRange(viewerComp.LastPingLocation.Value, viewerComp.ViewRange, _viewableEnts);
|
||||
foreach (var ore in _viewableEnts)
|
||||
{
|
||||
if (!_xformQuery.TryComp(ore, out var xform) ||
|
||||
!_spriteQuery.TryComp(ore, out var sprite))
|
||||
continue;
|
||||
|
||||
if (xform.MapID != args.MapId || !sprite.Visible)
|
||||
continue;
|
||||
|
||||
if (!sprite.LayerMapTryGet(MiningScannerVisualLayers.Overlay, out var idx))
|
||||
continue;
|
||||
var layer = sprite[idx];
|
||||
|
||||
if (layer.ActualRsi?.Path == null || layer.RsiState.Name == null)
|
||||
continue;
|
||||
|
||||
var gridRot = xform.GridUid == null ? 0 : _xformQuery.CompOrNull(xform.GridUid.Value)?.LocalRotation ?? 0;
|
||||
var rotationMatrix = Matrix3Helpers.CreateRotation(gridRot);
|
||||
|
||||
var worldMatrix = Matrix3Helpers.CreateTranslation(_xform.GetWorldPosition(xform));
|
||||
var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
|
||||
var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
|
||||
handle.SetTransform(matty);
|
||||
|
||||
var spriteSpec = new SpriteSpecifier.Rsi(layer.ActualRsi.Path, layer.RsiState.Name);
|
||||
var texture = _sprite.GetFrame(spriteSpec, TimeSpan.FromSeconds(layer.AnimationTime));
|
||||
|
||||
var animTime = (viewerComp.NextPingTime - _timing.CurTime).TotalSeconds;
|
||||
|
||||
|
||||
var alpha = animTime < viewerComp.AnimationDuration
|
||||
? 0
|
||||
: (float) Math.Clamp((animTime - viewerComp.AnimationDuration) / viewerComp.AnimationDuration, 0f, 1f);
|
||||
var color = Color.White.WithAlpha(alpha);
|
||||
|
||||
handle.DrawTexture(texture, -(Vector2) texture.Size / 2f / EyeManager.PixelsPerMeter, layer.Rotation, modulate: color);
|
||||
|
||||
}
|
||||
handle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
54
Content.Client/Mining/MiningOverlaySystem.cs
Normal file
54
Content.Client/Mining/MiningOverlaySystem.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Shared.Mining.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Mining;
|
||||
|
||||
/// <summary>
|
||||
/// This handles the lifetime of the <see cref="MiningOverlay"/> for a given entity.
|
||||
/// </summary>
|
||||
public sealed class MiningOverlaySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private MiningOverlay _overlay = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MiningScannerViewerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<MiningScannerViewerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<MiningScannerViewerComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<MiningScannerViewerComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
_overlay = new();
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(Entity<MiningScannerViewerComponent> ent, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<MiningScannerViewerComponent> ent, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnInit(Entity<MiningScannerViewerComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
if (_player.LocalEntity == ent)
|
||||
{
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<MiningScannerViewerComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (_player.LocalEntity == ent)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Content.Client.Mining;
|
||||
|
||||
public sealed class OreVeinVisualsComponent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Nutrition.EntitySystems;
|
||||
|
||||
@@ -50,6 +49,7 @@ public sealed class ClientFoodSequenceSystem : SharedFoodSequenceSystem
|
||||
sprite.AddBlankLayer(index);
|
||||
sprite.LayerMapSet(keyCode, index);
|
||||
sprite.LayerSetSprite(index, state.Sprite);
|
||||
sprite.LayerSetScale(index, state.Scale);
|
||||
|
||||
//Offset the layer
|
||||
var layerPos = start.Comp.StartPosition;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
|
||||
namespace Content.Client.Physics;
|
||||
|
||||
@@ -16,8 +15,6 @@ public sealed class JointVisualsOverlay : Overlay
|
||||
|
||||
private IEntityManager _entManager;
|
||||
|
||||
private HashSet<Joint> _drawn = new();
|
||||
|
||||
public JointVisualsOverlay(IEntityManager entManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
@@ -25,7 +22,6 @@ public sealed class JointVisualsOverlay : Overlay
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
_drawn.Clear();
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
var spriteSystem = _entManager.System<SpriteSystem>();
|
||||
@@ -33,12 +29,14 @@ public sealed class JointVisualsOverlay : Overlay
|
||||
var joints = _entManager.EntityQueryEnumerator<JointVisualsComponent, TransformComponent>();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
args.DrawingHandle.SetTransform(Matrix3x2.Identity);
|
||||
|
||||
while (joints.MoveNext(out var visuals, out var xform))
|
||||
{
|
||||
if (xform.MapID != args.MapId)
|
||||
continue;
|
||||
|
||||
var other = visuals.Target;
|
||||
var other = _entManager.GetEntity(visuals.Target);
|
||||
|
||||
if (!xformQuery.TryGetComponent(other, out var otherXform))
|
||||
continue;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Players;
|
||||
@@ -133,7 +133,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
||||
reasons.Add(jobReason.ToMarkup());
|
||||
}
|
||||
|
||||
reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkup(string.Join('\n', reasons));
|
||||
reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkupOrThrow(string.Join('\n', reasons));
|
||||
return reason == null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Power.PowerCharge;
|
||||
|
||||
public sealed class PowerChargeBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private PowerChargeWindow? _window;
|
||||
|
||||
public PowerChargeBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetPowerSwitch(bool on)
|
||||
{
|
||||
SendMessage(new SwitchChargingMachineMessage(on));
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
if (!EntMan.TryGetComponent(Owner, out PowerChargeComponent? component))
|
||||
return;
|
||||
|
||||
_window = this.CreateWindow<PowerChargeWindow>();
|
||||
_window.UpdateWindow(this, Loc.GetString(component.WindowTitle));
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (state is not PowerChargeState chargeState)
|
||||
return;
|
||||
|
||||
_window?.UpdateState(chargeState);
|
||||
}
|
||||
}
|
||||
10
Content.Client/Power/PowerCharge/PowerChargeComponent.cs
Normal file
10
Content.Client/Power/PowerCharge/PowerChargeComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.Power;
|
||||
|
||||
namespace Content.Client.Power.PowerCharge;
|
||||
|
||||
/// <inheritdoc cref="Content.Shared.Power.SharedPowerChargeComponent" />
|
||||
[RegisterComponent]
|
||||
public sealed partial class PowerChargeComponent : SharedPowerChargeComponent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,27 +1,26 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'gravity-generator-window-title'}"
|
||||
MinSize="270 130"
|
||||
SetSize="360 180">
|
||||
<BoxContainer Margin="4 0" Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<GridContainer Margin="2 0 0 0" Columns="2">
|
||||
<!-- Power -->
|
||||
<Label Text="{Loc 'gravity-generator-window-power'}" HorizontalExpand="True" StyleClasses="StatusFieldTitle" />
|
||||
<Label Text="{Loc 'power-charge-window-power'}" HorizontalExpand="True" StyleClasses="StatusFieldTitle" />
|
||||
<BoxContainer Orientation="Horizontal" MinWidth="120">
|
||||
<Button Name="OnButton" Text="{Loc 'gravity-generator-window-power-on'}" StyleClasses="OpenRight" />
|
||||
<Button Name="OffButton" Text="{Loc 'gravity-generator-window-power-off'}" StyleClasses="OpenLeft" />
|
||||
<Button Name="OnButton" Text="{Loc 'power-charge-window-power-on'}" StyleClasses="OpenRight" />
|
||||
<Button Name="OffButton" Text="{Loc 'power-charge-window-power-off'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
<Control /> <!-- Empty control to act as a spacer in the grid. -->
|
||||
<Label Name="PowerLabel" Text="0 / 0 W" />
|
||||
<!-- Status -->
|
||||
<Label Text="{Loc 'gravity-generator-window-status'}" StyleClasses="StatusFieldTitle" />
|
||||
<Label Name="StatusLabel" Text="{Loc 'gravity-generator-window-status-fully-charged'}" />
|
||||
<Label Text="{Loc 'power-charge-window-status'}" StyleClasses="StatusFieldTitle" />
|
||||
<Label Name="StatusLabel" Text="{Loc 'power-charge-window-status-fully-charged'}" />
|
||||
<!-- ETA -->
|
||||
<Label Text="{Loc 'gravity-generator-window-eta'}" StyleClasses="StatusFieldTitle" />
|
||||
<Label Text="{Loc 'power-charge-window-eta'}" StyleClasses="StatusFieldTitle" />
|
||||
<Label Name="EtaLabel" Text="N/A" />
|
||||
<!-- Charge -->
|
||||
<Label Text="{Loc 'gravity-generator-window-charge'}" StyleClasses="StatusFieldTitle" />
|
||||
<Label Text="{Loc 'power-charge-window-charge'}" StyleClasses="StatusFieldTitle" />
|
||||
<ProgressBar Name="ChargeBar" MaxValue="255">
|
||||
<Label Name="ChargeText" Margin="4 0" Text="0 %" />
|
||||
</ProgressBar>
|
||||
@@ -31,5 +30,4 @@
|
||||
<SpriteView Name="EntityView" SetSize="96 96" OverrideDirection="South" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
</controls:FancyWindow>
|
||||
72
Content.Client/Power/PowerCharge/PowerChargeWindow.xaml.cs
Normal file
72
Content.Client/Power/PowerCharge/PowerChargeWindow.xaml.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Power.PowerCharge;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PowerChargeWindow : FancyWindow
|
||||
{
|
||||
private readonly ButtonGroup _buttonGroup = new();
|
||||
|
||||
public PowerChargeWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
OnButton.Group = _buttonGroup;
|
||||
OffButton.Group = _buttonGroup;
|
||||
}
|
||||
|
||||
public void UpdateWindow(PowerChargeBoundUserInterface bui, string title)
|
||||
{
|
||||
Title = title;
|
||||
|
||||
OnButton.OnPressed += _ => bui.SetPowerSwitch(true);
|
||||
OffButton.OnPressed += _ => bui.SetPowerSwitch(false);
|
||||
|
||||
EntityView.SetEntity(bui.Owner);
|
||||
}
|
||||
|
||||
public void UpdateState(PowerChargeState state)
|
||||
{
|
||||
if (state.On)
|
||||
OnButton.Pressed = true;
|
||||
else
|
||||
OffButton.Pressed = true;
|
||||
|
||||
PowerLabel.Text = Loc.GetString(
|
||||
"power-charge-window-power-label",
|
||||
("draw", state.PowerDraw),
|
||||
("max", state.PowerDrawMax));
|
||||
|
||||
PowerLabel.SetOnlyStyleClass(MathHelper.CloseTo(state.PowerDraw, state.PowerDrawMax) ? "Good" : "Caution");
|
||||
|
||||
ChargeBar.Value = state.Charge;
|
||||
ChargeText.Text = (state.Charge / 255f).ToString("P0");
|
||||
StatusLabel.Text = Loc.GetString(state.PowerStatus switch
|
||||
{
|
||||
PowerChargePowerStatus.Off => "power-charge-window-status-off",
|
||||
PowerChargePowerStatus.Discharging => "power-charge-window-status-discharging",
|
||||
PowerChargePowerStatus.Charging => "power-charge-window-status-charging",
|
||||
PowerChargePowerStatus.FullyCharged => "power-charge-window-status-fully-charged",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
});
|
||||
|
||||
StatusLabel.SetOnlyStyleClass(state.PowerStatus switch
|
||||
{
|
||||
PowerChargePowerStatus.Off => "Danger",
|
||||
PowerChargePowerStatus.Discharging => "Caution",
|
||||
PowerChargePowerStatus.Charging => "Caution",
|
||||
PowerChargePowerStatus.FullyCharged => "Good",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
});
|
||||
|
||||
EtaLabel.Text = state.EtaSeconds >= 0
|
||||
? Loc.GetString("power-charge-window-eta-value", ("left", TimeSpan.FromSeconds(state.EtaSeconds)))
|
||||
: Loc.GetString("power-charge-window-eta-none");
|
||||
|
||||
EtaLabel.SetOnlyStyleClass(state.EtaSeconds >= 0 ? "Caution" : "Disabled");
|
||||
}
|
||||
}
|
||||
@@ -309,7 +309,7 @@ public sealed partial class PowerMonitoringWindow
|
||||
BorderThickness = new Thickness(2),
|
||||
};
|
||||
|
||||
msg.AddMarkup(Loc.GetString("power-monitoring-window-rogue-power-consumer"));
|
||||
msg.AddMarkupOrThrow(Loc.GetString("power-monitoring-window-rogue-power-consumer"));
|
||||
SystemWarningPanel.Visible = true;
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ public sealed partial class PowerMonitoringWindow
|
||||
BorderThickness = new Thickness(2),
|
||||
};
|
||||
|
||||
msg.AddMarkup(Loc.GetString("power-monitoring-window-power-net-abnormalities"));
|
||||
msg.AddMarkupOrThrow(Loc.GetString("power-monitoring-window-power-net-abnormalities"));
|
||||
SystemWarningPanel.Visible = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,12 +128,12 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
|
||||
};
|
||||
|
||||
var text = new FormattedMessage();
|
||||
text.PushMarkup(Loc.GetString("robotics-console-model", ("name", model)));
|
||||
text.AddMarkup(Loc.GetString("robotics-console-designation"));
|
||||
text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-model", ("name", model))}\n");
|
||||
text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation"));
|
||||
text.AddText($" {data.Name}\n"); // prevent players trolling by naming borg [color=red]satan[/color]
|
||||
text.PushMarkup(Loc.GetString("robotics-console-battery", ("charge", (int) (data.Charge * 100f)), ("color", batteryColor)));
|
||||
text.PushMarkup(Loc.GetString("robotics-console-brain", ("brain", data.HasBrain)));
|
||||
text.AddMarkup(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount)));
|
||||
text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-battery", ("charge", (int)(data.Charge * 100f)), ("color", batteryColor))}\n");
|
||||
text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-brain", ("brain", data.HasBrain))}\n");
|
||||
text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount)));
|
||||
BorgInfo.SetMessage(text);
|
||||
|
||||
// how the turntables
|
||||
|
||||
@@ -61,9 +61,9 @@ namespace Content.Client.RoundEnd
|
||||
//Gamemode Name
|
||||
var gamemodeLabel = new RichTextLabel();
|
||||
var gamemodeMessage = new FormattedMessage();
|
||||
gamemodeMessage.AddMarkup(Loc.GetString("round-end-summary-window-round-id-label", ("roundId", roundId)));
|
||||
gamemodeMessage.AddMarkupOrThrow(Loc.GetString("round-end-summary-window-round-id-label", ("roundId", roundId)));
|
||||
gamemodeMessage.AddText(" ");
|
||||
gamemodeMessage.AddMarkup(Loc.GetString("round-end-summary-window-gamemode-name-label", ("gamemode", gamemode)));
|
||||
gamemodeMessage.AddMarkupOrThrow(Loc.GetString("round-end-summary-window-gamemode-name-label", ("gamemode", gamemode)));
|
||||
gamemodeLabel.SetMessage(gamemodeMessage);
|
||||
roundEndSummaryContainer.AddChild(gamemodeLabel);
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ public sealed partial class StationAiMenu : RadialMenu
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
public event Action<BaseStationAiAction>? OnAiRadial;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
private string _search = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
private HashSet<ListingData> _listings = new();
|
||||
private HashSet<ListingDataWithCostModifiers> _listings = new();
|
||||
|
||||
public StoreBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
@@ -33,7 +33,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
|
||||
_menu.OnListingButtonPressed += (_, listing) =>
|
||||
{
|
||||
SendMessage(new StoreBuyListingMessage(listing));
|
||||
SendMessage(new StoreBuyListingMessage(listing.ID));
|
||||
};
|
||||
|
||||
_menu.OnCategoryButtonPressed += (_, category) =>
|
||||
@@ -68,6 +68,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
_listings = msg.Listings;
|
||||
|
||||
_menu?.UpdateBalance(msg.Balance);
|
||||
|
||||
UpdateListingsWithSearchFilter();
|
||||
_menu?.SetFooterVisibility(msg.ShowFooter);
|
||||
_menu?.UpdateRefund(msg.AllowRefund);
|
||||
@@ -80,7 +81,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
var filteredListings = new HashSet<ListingData>(_listings);
|
||||
var filteredListings = new HashSet<ListingDataWithCostModifiers>(_listings);
|
||||
if (!string.IsNullOrEmpty(_search))
|
||||
{
|
||||
filteredListings.RemoveWhere(listingData => !ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search) &&
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<BoxContainer Margin="8,8,8,8" Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="StoreItemName" HorizontalExpand="True" />
|
||||
<Label Name="DiscountSubText"
|
||||
HorizontalAlignment="Right"/>
|
||||
<Button
|
||||
Name="StoreItemBuyButton"
|
||||
MinWidth="64"
|
||||
|
||||
@@ -17,11 +17,12 @@ public sealed partial class StoreListingControl : Control
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
private readonly ClientGameTicker _ticker;
|
||||
|
||||
private readonly ListingData _data;
|
||||
private readonly ListingDataWithCostModifiers _data;
|
||||
|
||||
private readonly bool _hasBalance;
|
||||
private readonly string _price;
|
||||
public StoreListingControl(ListingData data, string price, bool hasBalance, Texture? texture = null)
|
||||
private readonly string _discount;
|
||||
public StoreListingControl(ListingDataWithCostModifiers data, string price, string discount, bool hasBalance, Texture? texture = null)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -31,6 +32,7 @@ public sealed partial class StoreListingControl : Control
|
||||
_data = data;
|
||||
_hasBalance = hasBalance;
|
||||
_price = price;
|
||||
_discount = discount;
|
||||
|
||||
StoreItemName.Text = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
|
||||
StoreItemDescription.SetMessage(ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(_data, _prototype));
|
||||
@@ -63,6 +65,7 @@ public sealed partial class StoreListingControl : Control
|
||||
}
|
||||
else
|
||||
{
|
||||
DiscountSubText.Text = _discount;
|
||||
StoreItemBuyButton.Text = _price;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.FixedPoint;
|
||||
@@ -22,7 +23,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
private StoreWithdrawWindow? _withdrawWindow;
|
||||
|
||||
public event EventHandler<string>? SearchTextUpdated;
|
||||
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, ListingDataWithCostModifiers>? OnListingButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
|
||||
public event Action<BaseButton.ButtonEventArgs>? OnRefundAttempt;
|
||||
@@ -30,7 +31,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
public Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> Balance = new();
|
||||
public string CurrentCategory = string.Empty;
|
||||
|
||||
private List<ListingData> _cachedListings = new();
|
||||
private List<ListingDataWithCostModifiers> _cachedListings = new();
|
||||
|
||||
public StoreMenu()
|
||||
{
|
||||
@@ -68,15 +69,17 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
WithdrawButton.Disabled = disabled;
|
||||
}
|
||||
|
||||
public void UpdateListing(List<ListingData> listings)
|
||||
public void UpdateListing(List<ListingDataWithCostModifiers> listings)
|
||||
{
|
||||
_cachedListings = listings;
|
||||
|
||||
UpdateListing();
|
||||
}
|
||||
|
||||
public void UpdateListing()
|
||||
{
|
||||
var sorted = _cachedListings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
|
||||
var sorted = _cachedListings.OrderBy(l => l.Priority)
|
||||
.ThenBy(l => l.Cost.Values.Sum());
|
||||
|
||||
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
||||
// maybe read clients prototypes instead?
|
||||
@@ -114,13 +117,12 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
OnRefundAttempt?.Invoke(args);
|
||||
}
|
||||
|
||||
private void AddListingGui(ListingData listing)
|
||||
private void AddListingGui(ListingDataWithCostModifiers listing)
|
||||
{
|
||||
if (!listing.Categories.Contains(CurrentCategory))
|
||||
return;
|
||||
|
||||
var listingPrice = listing.Cost;
|
||||
var hasBalance = HasListingPrice(Balance, listingPrice);
|
||||
var hasBalance = listing.CanBuyWith(Balance);
|
||||
|
||||
var spriteSys = _entityManager.EntitySysManager.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
@@ -143,29 +145,20 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
}
|
||||
}
|
||||
|
||||
var newListing = new StoreListingControl(listing, GetListingPriceString(listing), hasBalance, texture);
|
||||
var listingInStock = GetListingPriceString(listing);
|
||||
var discount = GetDiscountString(listing);
|
||||
|
||||
var newListing = new StoreListingControl(listing, listingInStock, discount, hasBalance, texture);
|
||||
newListing.StoreItemBuyButton.OnButtonDown += args
|
||||
=> OnListingButtonPressed?.Invoke(args, listing);
|
||||
|
||||
StoreListingsContainer.AddChild(newListing);
|
||||
}
|
||||
|
||||
public bool HasListingPrice(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> currency, Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> price)
|
||||
{
|
||||
foreach (var type in price)
|
||||
{
|
||||
if (!currency.ContainsKey(type.Key))
|
||||
return false;
|
||||
|
||||
if (currency[type.Key] < type.Value)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetListingPriceString(ListingData listing)
|
||||
private string GetListingPriceString(ListingDataWithCostModifiers listing)
|
||||
{
|
||||
var text = string.Empty;
|
||||
|
||||
if (listing.Cost.Count < 1)
|
||||
text = Loc.GetString("store-currency-free");
|
||||
else
|
||||
@@ -173,20 +166,72 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
foreach (var (type, amount) in listing.Cost)
|
||||
{
|
||||
var currency = _prototypeManager.Index(type);
|
||||
text += Loc.GetString("store-ui-price-display", ("amount", amount),
|
||||
("currency", Loc.GetString(currency.DisplayName, ("amount", amount))));
|
||||
|
||||
text += Loc.GetString(
|
||||
"store-ui-price-display",
|
||||
("amount", amount),
|
||||
("currency", Loc.GetString(currency.DisplayName, ("amount", amount)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return text.TrimEnd();
|
||||
}
|
||||
|
||||
private string GetDiscountString(ListingDataWithCostModifiers listingDataWithCostModifiers)
|
||||
{
|
||||
string discountMessage;
|
||||
|
||||
if (!listingDataWithCostModifiers.IsCostModified)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var relativeModifiersSummary = listingDataWithCostModifiers.GetModifiersSummaryRelative();
|
||||
if (relativeModifiersSummary.Count > 1)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('(');
|
||||
foreach (var (currency, amount) in relativeModifiersSummary)
|
||||
{
|
||||
var currencyPrototype = _prototypeManager.Index(currency);
|
||||
if (sb.Length != 0)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
var currentDiscountMessage = Loc.GetString(
|
||||
"store-ui-discount-display-with-currency",
|
||||
("amount", amount.ToString("P0")),
|
||||
("currency", Loc.GetString(currencyPrototype.DisplayName))
|
||||
);
|
||||
sb.Append(currentDiscountMessage);
|
||||
}
|
||||
|
||||
sb.Append(')');
|
||||
discountMessage = sb.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// if cost was modified - it should have diff relatively to original cost in 1 or more currency
|
||||
// ReSharper disable once GenericEnumeratorNotDisposed Dictionary enumerator doesn't require dispose
|
||||
var enumerator = relativeModifiersSummary.GetEnumerator();
|
||||
enumerator.MoveNext();
|
||||
var amount = enumerator.Current.Value;
|
||||
discountMessage = Loc.GetString(
|
||||
"store-ui-discount-display",
|
||||
("amount", (amount.ToString("P0")))
|
||||
);
|
||||
}
|
||||
|
||||
return discountMessage;
|
||||
}
|
||||
|
||||
private void ClearListings()
|
||||
{
|
||||
StoreListingsContainer.Children.Clear();
|
||||
}
|
||||
|
||||
public void PopulateStoreCategoryButtons(HashSet<ListingData> listings)
|
||||
public void PopulateStoreCategoryButtons(HashSet<ListingDataWithCostModifiers> listings)
|
||||
{
|
||||
var allCategories = new List<StoreCategoryPrototype>();
|
||||
foreach (var listing in listings)
|
||||
|
||||
@@ -25,7 +25,15 @@ public sealed partial class DefaultGameScreen : InGameScreen
|
||||
|
||||
Chat.OnResized += ChatOnResized;
|
||||
Chat.OnChatResizeFinish += ChatOnResizeFinish;
|
||||
Actions.ActionsContainer.Columns = 1;
|
||||
|
||||
MainViewport.OnResized += ResizeActionContainer;
|
||||
Inventory.OnResized += ResizeActionContainer;
|
||||
}
|
||||
|
||||
private void ResizeActionContainer()
|
||||
{
|
||||
float indent = Inventory.Size.Y + TopBar.Size.Y + 40;
|
||||
Actions.ActionsContainer.MaxGridHeight = MainViewport.Size.Y - indent;
|
||||
}
|
||||
|
||||
private void ChatOnResizeFinish(Vector2 _)
|
||||
|
||||
@@ -26,6 +26,14 @@ public sealed partial class SeparatedChatGameScreen : InGameScreen
|
||||
|
||||
ScreenContainer.OnSplitResizeFinished += () =>
|
||||
OnChatResized?.Invoke(new Vector2(ScreenContainer.SplitFraction, 0));
|
||||
|
||||
ViewportContainer.OnResized += ResizeActionContainer;
|
||||
}
|
||||
|
||||
private void ResizeActionContainer()
|
||||
{
|
||||
float indent = 20;
|
||||
Actions.ActionsContainer.MaxGridWidth = ViewportContainer.Size.X - indent;
|
||||
}
|
||||
|
||||
public override ChatBox ChatBox => GetWidget<ChatBox>()!;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Content.Client.Actions.UI;
|
||||
using Content.Client.Cooldown;
|
||||
using Content.Shared.Alert;
|
||||
@@ -69,8 +69,8 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
||||
|
||||
private Control SupplyTooltip(Control? sender)
|
||||
{
|
||||
var msg = FormattedMessage.FromMarkup(Loc.GetString(Alert.Name));
|
||||
var desc = FormattedMessage.FromMarkup(Loc.GetString(Alert.Description));
|
||||
var msg = FormattedMessage.FromMarkupOrThrow(Loc.GetString(Alert.Name));
|
||||
var desc = FormattedMessage.FromMarkupOrThrow(Loc.GetString(Alert.Description));
|
||||
return new ActionAlertTooltip(msg, desc) {Cooldown = Cooldown};
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ public partial class ChatBox : UIWidget
|
||||
{
|
||||
var formatted = new FormattedMessage(3);
|
||||
formatted.PushColor(color);
|
||||
formatted.AddMarkup(message);
|
||||
formatted.AddMarkupOrThrow(message);
|
||||
formatted.Pop();
|
||||
Contents.AddMessage(formatted);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.VendingMachines.UI;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using System.Linq;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.VendingMachines
|
||||
{
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Content.Client.Verbs
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly ExamineSystem _examine = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
@@ -139,11 +139,11 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
{
|
||||
if (state.Paused)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("analysis-console-info-scanner-paused"));
|
||||
message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-scanner-paused"));
|
||||
}
|
||||
else
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("analysis-console-info-scanner"));
|
||||
message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-scanner"));
|
||||
}
|
||||
Information.SetMessage(message);
|
||||
UpdateArtifactIcon(null); //set it to blank
|
||||
@@ -155,11 +155,11 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
if (state.ScanReport == null)
|
||||
{
|
||||
if (!state.AnalyzerConnected) //no analyzer connected
|
||||
message.AddMarkup(Loc.GetString("analysis-console-info-no-scanner"));
|
||||
message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-no-scanner"));
|
||||
else if (!state.CanScan) //no artifact
|
||||
message.AddMarkup(Loc.GetString("analysis-console-info-no-artifact"));
|
||||
message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-no-artifact"));
|
||||
else if (state.Artifact == null) //ready to go
|
||||
message.AddMarkup(Loc.GetString("analysis-console-info-ready"));
|
||||
message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-ready"));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
8
Content.IntegrationTests/AssemblyInfo.cs
Normal file
8
Content.IntegrationTests/AssemblyInfo.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
[assembly: Parallelizable(ParallelScope.Children)]
|
||||
|
||||
// I don't know why this parallelism limit was originally put here.
|
||||
// I *do* know that I tried removing it, and ran into the following .NET runtime problem:
|
||||
// https://github.com/dotnet/runtime/issues/107197
|
||||
// So we can't really parallelize integration tests harder either until the runtime fixes that,
|
||||
// *or* we fix serv3 to not spam expression trees.
|
||||
[assembly: LevelOfParallelism(3)]
|
||||
@@ -23,8 +23,6 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
[assembly: LevelOfParallelism(3)]
|
||||
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
|
||||
[assembly: Parallelizable(ParallelScope.Children)]
|
||||
|
||||
namespace Content.IntegrationTests;
|
||||
namespace Content.IntegrationTests;
|
||||
|
||||
[SetUpFixture]
|
||||
public sealed class PoolManagerTestEventHandler
|
||||
|
||||
@@ -32,11 +32,14 @@ public sealed class ActionPvsDetachTest
|
||||
// PVS-detach action entities
|
||||
// We do this by just giving them the ghost layer
|
||||
var visSys = server.System<VisibilitySystem>();
|
||||
var enumerator = server.Transform(ent).ChildEnumerator;
|
||||
while (enumerator.MoveNext(out var child))
|
||||
server.Post(() =>
|
||||
{
|
||||
visSys.AddLayer(child, (int) VisibilityFlags.Ghost);
|
||||
}
|
||||
var enumerator = server.Transform(ent).ChildEnumerator;
|
||||
while (enumerator.MoveNext(out var child))
|
||||
{
|
||||
visSys.AddLayer(child, (int) VisibilityFlags.Ghost);
|
||||
}
|
||||
});
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
// Client's actions have left been detached / are out of view, but action comp state has not changed
|
||||
@@ -44,11 +47,14 @@ public sealed class ActionPvsDetachTest
|
||||
Assert.That(cSys.GetActions(cEnt).Count(), Is.EqualTo(initActions));
|
||||
|
||||
// Re-enter PVS view
|
||||
enumerator = server.Transform(ent).ChildEnumerator;
|
||||
while (enumerator.MoveNext(out var child))
|
||||
server.Post(() =>
|
||||
{
|
||||
visSys.RemoveLayer(child, (int) VisibilityFlags.Ghost);
|
||||
}
|
||||
var enumerator = server.Transform(ent).ChildEnumerator;
|
||||
while (enumerator.MoveNext(out var child))
|
||||
{
|
||||
visSys.RemoveLayer(child, (int) VisibilityFlags.Ghost);
|
||||
}
|
||||
});
|
||||
await pair.RunTicksSync(5);
|
||||
Assert.That(sys.GetActions(ent).Count(), Is.EqualTo(initActions));
|
||||
Assert.That(cSys.GetActions(cEnt).Count(), Is.EqualTo(initActions));
|
||||
|
||||
@@ -34,7 +34,11 @@ public sealed class BuckleDragTest : InteractionTest
|
||||
Assert.That(pullable.BeingPulled, Is.False);
|
||||
|
||||
// Strap the human to the chair
|
||||
Assert.That(Server.System<SharedBuckleSystem>().TryBuckle(sUrist, SPlayer, STarget.Value));
|
||||
await Server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(Server.System<SharedBuckleSystem>().TryBuckle(sUrist, SPlayer, STarget.Value));
|
||||
});
|
||||
|
||||
await RunTicks(5);
|
||||
Assert.That(buckle.Buckled, Is.True);
|
||||
Assert.That(buckle.BuckledTo, Is.EqualTo(STarget));
|
||||
|
||||
@@ -253,13 +253,16 @@ public sealed class CargoTest
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var priceSystem = entManager.System<PricingSystem>();
|
||||
|
||||
var ent = entManager.SpawnEntity("StackEnt", MapCoordinates.Nullspace);
|
||||
var price = priceSystem.GetPrice(ent);
|
||||
Assert.That(price, Is.EqualTo(100.0));
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var priceSystem = entManager.System<PricingSystem>();
|
||||
|
||||
var ent = entManager.SpawnEntity("StackEnt", MapCoordinates.Nullspace);
|
||||
var price = priceSystem.GetPrice(ent);
|
||||
Assert.That(price, Is.EqualTo(100.0));
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ public sealed class SuicideCommandTests
|
||||
mobStateComp = entManager.GetComponent<MobStateComponent>(player);
|
||||
mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
|
||||
damageableComp = entManager.GetComponent<DamageableComponent>(player);
|
||||
});
|
||||
|
||||
if (protoMan.TryIndex<DamageTypePrototype>("Slash", out var slashProto))
|
||||
damageableSystem.TryChangeDamage(player, new DamageSpecifier(slashProto, FixedPoint2.New(46.5)));
|
||||
if (protoMan.TryIndex<DamageTypePrototype>("Slash", out var slashProto))
|
||||
damageableSystem.TryChangeDamage(player, new DamageSpecifier(slashProto, FixedPoint2.New(46.5)));
|
||||
});
|
||||
|
||||
// Check that running the suicide command kills the player
|
||||
// and properly ghosts them without them being able to return to their body
|
||||
|
||||
@@ -25,6 +25,9 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
id: WeightlessGravityGeneratorDummy
|
||||
components:
|
||||
- type: GravityGenerator
|
||||
- type: PowerCharge
|
||||
windowTitle: gravity-generator-window-title
|
||||
idlePower: 50
|
||||
chargeRate: 1000000000 # Set this really high so it discharges in a single tick.
|
||||
activePower: 500
|
||||
- type: ApcPowerReceiver
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace Content.IntegrationTests.Tests
|
||||
id: GridGravityGeneratorDummy
|
||||
components:
|
||||
- type: GravityGenerator
|
||||
- type: PowerCharge
|
||||
windowTitle: gravity-generator-window-title
|
||||
idlePower: 50
|
||||
chargeRate: 1000000000 # Set this really high so it discharges in a single tick.
|
||||
activePower: 500
|
||||
- type: ApcPowerReceiver
|
||||
|
||||
@@ -44,6 +44,7 @@ public sealed class MaterialArbitrageTest
|
||||
var pricing = entManager.System<PricingSystem>();
|
||||
var stackSys = entManager.System<StackSystem>();
|
||||
var mapSystem = server.System<SharedMapSystem>();
|
||||
var latheSys = server.System<SharedLatheSystem>();
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
|
||||
Assert.That(mapSystem.IsInitialized(testMap.MapId));
|
||||
@@ -53,12 +54,8 @@ public sealed class MaterialArbitrageTest
|
||||
var materialName = compFact.GetComponentName(typeof(MaterialComponent));
|
||||
var destructibleName = compFact.GetComponentName(typeof(DestructibleComponent));
|
||||
|
||||
// construct inverted lathe recipe dictionary
|
||||
Dictionary<string, List<LatheRecipePrototype>> latheRecipes = new();
|
||||
foreach (var proto in protoManager.EnumeratePrototypes<LatheRecipePrototype>())
|
||||
{
|
||||
latheRecipes.GetOrNew(proto.Result).Add(proto);
|
||||
}
|
||||
// get the inverted lathe recipe dictionary
|
||||
var latheRecipes = latheSys.InverseRecipes;
|
||||
|
||||
// Lets assume the possible lathe for resource multipliers:
|
||||
// TODO: each recipe can technically have its own cost multiplier associated with it, so this test needs redone to factor that in.
|
||||
|
||||
@@ -97,13 +97,14 @@ public sealed class DockTest : ContentUnitTest
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var dockingSystem = entManager.System<DockingSystem>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
MapGridComponent mapGrid = default!;
|
||||
|
||||
var mapGrid = entManager.AddComponent<MapGridComponent>(map.MapUid);
|
||||
var shuttle = EntityUid.Invalid;
|
||||
|
||||
// Spawn shuttle and affirm no valid docks.
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
mapGrid = entManager.AddComponent<MapGridComponent>(map.MapUid);
|
||||
entManager.DeleteEntity(map.Grid);
|
||||
Assert.That(entManager.System<MapLoaderSystem>().TryLoad(otherMap.MapId, "/Maps/Shuttles/emergency.yml", out var rootUids));
|
||||
shuttle = rootUids[0];
|
||||
|
||||
160
Content.IntegrationTests/Tests/StoreTests.cs
Normal file
160
Content.IntegrationTests/Tests/StoreTests.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Store.Components;
|
||||
using Content.Shared.StoreDiscount.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class StoreTests
|
||||
{
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
name: InventoryPdaDummy
|
||||
id: InventoryPdaDummy
|
||||
parent: BasePDA
|
||||
components:
|
||||
- type: Clothing
|
||||
QuickEquip: false
|
||||
slots:
|
||||
- idcard
|
||||
- type: Pda
|
||||
";
|
||||
[Test]
|
||||
public async Task StoreDiscountAndRefund()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var testMap = await pair.CreateTestMap();
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var serverRandom = server.ResolveDependency<IRobustRandom>();
|
||||
serverRandom.SetSeed(534);
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
|
||||
var mapSystem = server.System<SharedMapSystem>();
|
||||
var prototypeManager = server.ProtoMan;
|
||||
|
||||
Assert.That(mapSystem.IsInitialized(testMap.MapId));
|
||||
|
||||
|
||||
EntityUid human = default;
|
||||
EntityUid uniform = default;
|
||||
EntityUid pda = default;
|
||||
|
||||
var uplinkSystem = entManager.System<UplinkSystem>();
|
||||
|
||||
var listingPrototypes = prototypeManager.EnumeratePrototypes<ListingPrototype>()
|
||||
.ToArray();
|
||||
|
||||
var coordinates = testMap.GridCoords;
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var invSystem = entManager.System<InventorySystem>();
|
||||
|
||||
human = entManager.SpawnEntity("HumanUniformDummy", coordinates);
|
||||
uniform = entManager.SpawnEntity("UniformDummy", coordinates);
|
||||
pda = entManager.SpawnEntity("InventoryPdaDummy", coordinates);
|
||||
|
||||
Assert.That(invSystem.TryEquip(human, uniform, "jumpsuit"));
|
||||
Assert.That(invSystem.TryEquip(human, pda, "id"));
|
||||
|
||||
FixedPoint2 originalBalance = 20;
|
||||
uplinkSystem.AddUplink(human, originalBalance, null, true);
|
||||
|
||||
var storeComponent = entManager.GetComponent<StoreComponent>(pda);
|
||||
var discountComponent = entManager.GetComponent<StoreDiscountComponent>(pda);
|
||||
Assert.That(
|
||||
discountComponent.Discounts,
|
||||
Has.Exactly(3).Items,
|
||||
$"After applying discount total discounted items count was expected to be '3' "
|
||||
+ $"but was actually {discountComponent.Discounts.Count}- this can be due to discount "
|
||||
+ $"categories settings (maxItems, weight) not being realistically set, or default "
|
||||
+ $"discounted count being changed from '3' in StoreDiscountSystem.InitializeDiscounts."
|
||||
);
|
||||
var discountedListingItems = storeComponent.FullListingsCatalog
|
||||
.Where(x => x.IsCostModified)
|
||||
.OrderBy(x => x.ID)
|
||||
.ToArray();
|
||||
Assert.That(discountComponent.Discounts
|
||||
.Select(x => x.ListingId.Id),
|
||||
Is.EquivalentTo(discountedListingItems.Select(x => x.ID)),
|
||||
$"{nameof(StoreComponent)}.{nameof(StoreComponent.FullListingsCatalog)} does not contain all "
|
||||
+ $"items that are marked as discounted, or they don't have flag '{nameof(ListingDataWithCostModifiers.IsCostModified)}'"
|
||||
+ $"flag as 'true'. This marks the fact that cost modifier of discount is not applied properly!"
|
||||
);
|
||||
|
||||
// Refund action requests re-generation of listing items so we will be re-acquiring items from component a lot of times.
|
||||
var itemIds = discountedListingItems.Select(x => x.ID);
|
||||
foreach (var itemId in itemIds)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
storeComponent.RefundAllowed = true;
|
||||
|
||||
var discountedListingItem = storeComponent.FullListingsCatalog.First(x => x.ID == itemId);
|
||||
var plainDiscountedCost = discountedListingItem.Cost[UplinkSystem.TelecrystalCurrencyPrototype];
|
||||
|
||||
var prototype = listingPrototypes.First(x => x.ID == discountedListingItem.ID);
|
||||
|
||||
var prototypeCost = prototype.Cost[UplinkSystem.TelecrystalCurrencyPrototype];
|
||||
var discountDownTo = prototype.DiscountDownTo[UplinkSystem.TelecrystalCurrencyPrototype];
|
||||
Assert.That(plainDiscountedCost.Value, Is.GreaterThanOrEqualTo(discountDownTo.Value), "Expected discounted cost to be greater then DiscountDownTo value.");
|
||||
Assert.That(plainDiscountedCost.Value, Is.LessThan(prototypeCost.Value), "Expected discounted cost to be lower then prototype cost.");
|
||||
|
||||
|
||||
var buyMsg = new StoreBuyListingMessage(discountedListingItem.ID){Actor = human};
|
||||
server.EntMan.EventBus.RaiseComponentEvent(pda, storeComponent, buyMsg);
|
||||
|
||||
var newBalance = storeComponent.Balance[UplinkSystem.TelecrystalCurrencyPrototype];
|
||||
Assert.That(newBalance.Value, Is.EqualTo((originalBalance - plainDiscountedCost).Value), "Expected to have balance reduced by discounted cost");
|
||||
Assert.That(
|
||||
discountedListingItem.IsCostModified,
|
||||
Is.False,
|
||||
$"Expected item cost to not be modified after Buying discounted item."
|
||||
);
|
||||
var costAfterBuy = discountedListingItem.Cost[UplinkSystem.TelecrystalCurrencyPrototype];
|
||||
Assert.That(costAfterBuy.Value, Is.EqualTo(prototypeCost.Value), "Expected cost after discount refund to be equal to prototype cost.");
|
||||
|
||||
var refundMsg = new StoreRequestRefundMessage { Actor = human };
|
||||
server.EntMan.EventBus.RaiseComponentEvent(pda, storeComponent, refundMsg);
|
||||
|
||||
// get refreshed item after refund re-generated items
|
||||
discountedListingItem = storeComponent.FullListingsCatalog.First(x => x.ID == itemId);
|
||||
|
||||
var afterRefundBalance = storeComponent.Balance[UplinkSystem.TelecrystalCurrencyPrototype];
|
||||
Assert.That(afterRefundBalance.Value, Is.EqualTo(originalBalance.Value), "Expected refund to return all discounted cost value.");
|
||||
Assert.That(
|
||||
discountComponent.Discounts.First(x => x.ListingId == discountedListingItem.ID).Count,
|
||||
Is.EqualTo(0),
|
||||
"Discounted count should still be zero even after refund."
|
||||
);
|
||||
|
||||
Assert.That(
|
||||
discountedListingItem.IsCostModified,
|
||||
Is.False,
|
||||
$"Expected item cost to not be modified after Buying discounted item (even after refund was done)."
|
||||
);
|
||||
var costAfterRefund = discountedListingItem.Cost[UplinkSystem.TelecrystalCurrencyPrototype];
|
||||
Assert.That(costAfterRefund.Value, Is.EqualTo(prototypeCost.Value), "Expected cost after discount refund to be equal to prototype cost.");
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<IdCardComponent, BeingMicrowavedEvent>(OnMicrowaved);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ public sealed class BanPanelEui : BaseEui
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly IAdminManager _admins = default!;
|
||||
|
||||
@@ -132,13 +131,12 @@ public sealed class BanPanelEui : BaseEui
|
||||
}
|
||||
|
||||
if (erase &&
|
||||
targetUid != null &&
|
||||
_playerManager.TryGetSessionById(targetUid.Value, out var targetPlayer))
|
||||
targetUid != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_entities.TrySystem(out AdminSystem? adminSystem))
|
||||
adminSystem.Erase(targetPlayer);
|
||||
adminSystem.Erase(targetUid.Value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
@@ -42,7 +42,7 @@ namespace Content.Server.Administration.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var solutionContainerSystem = _entManager.System<SolutionContainerSystem>();
|
||||
var solutionContainerSystem = _entManager.System<SharedSolutionContainerSystem>();
|
||||
if (!solutionContainerSystem.TryGetSolution((uid.Value, man), args[1], out var solution))
|
||||
{
|
||||
var validSolutions = string.Join(", ", solutionContainerSystem.EnumerateSolutions((uid.Value, man)).Select(s => s.Name));
|
||||
|
||||
47
Content.Server/Administration/Commands/EraseCommand.cs
Normal file
47
Content.Server/Administration/Commands/EraseCommand.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class EraseCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IPlayerLocator _locator = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly AdminSystem _admin = default!;
|
||||
|
||||
public override string Command => "erase";
|
||||
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-erase-invalid-args"));
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var located = await _locator.LookupIdByNameOrIdAsync(args[0]);
|
||||
|
||||
if (located == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-erase-player-not-found"));
|
||||
return;
|
||||
}
|
||||
|
||||
_admin.Erase(located.UserId);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var options = _players.Sessions.OrderBy(c => c.Name).Select(c => c.Name).ToArray();
|
||||
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-erase-player-completion"));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.FixedPoint;
|
||||
@@ -36,7 +36,7 @@ namespace Content.Server.Administration.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var solutionContainerSystem = _entManager.System<SolutionContainerSystem>();
|
||||
var solutionContainerSystem = _entManager.System<SharedSolutionContainerSystem>();
|
||||
if (!solutionContainerSystem.TryGetSolution((uid.Value, man), args[1], out var solution))
|
||||
{
|
||||
var validSolutions = string.Join(", ", solutionContainerSystem.EnumerateSolutions((uid.Value, man)).Select(s => s.Name));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Robust.Shared.Console;
|
||||
@@ -35,7 +35,7 @@ namespace Content.Server.Administration.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var solutionContainerSystem = _entManager.System<SolutionContainerSystem>();
|
||||
var solutionContainerSystem = _entManager.System<SharedSolutionContainerSystem>();
|
||||
if (!solutionContainerSystem.TryGetSolution((uid.Value, man), args[1], out var solution))
|
||||
{
|
||||
var validSolutions = string.Join(", ", solutionContainerSystem.EnumerateSolutions((uid.Value, man)).Select(s => s.Name));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Robust.Shared.Console;
|
||||
@@ -35,7 +35,7 @@ namespace Content.Server.Administration.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var solutionContainerSystem = _entManager.System<SolutionContainerSystem>();
|
||||
var solutionContainerSystem = _entManager.System<SharedSolutionContainerSystem>();
|
||||
if (!solutionContainerSystem.TryGetSolution((uid.Value, man), args[1], out var solutionEnt, out var solution))
|
||||
{
|
||||
var validSolutions = string.Join(", ", solutionContainerSystem.EnumerateSolutions((uid.Value, man)).Select(s => s.Name));
|
||||
|
||||
@@ -658,7 +658,7 @@ public record struct CommandPermissionsUnassignedError(CommandSpec Command) : IC
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup($"The command {Command.FullName()} is missing permission flags and cannot be executed.");
|
||||
return FormattedMessage.FromMarkupOrThrow($"The command {Command.FullName()} is missing permission flags and cannot be executed.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
@@ -671,7 +671,7 @@ public record struct NoPermissionError(CommandSpec Command) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup($"You do not have permission to execute {Command.FullName()}");
|
||||
return FormattedMessage.FromMarkupOrThrow($"You do not have permission to execute {Command.FullName()}");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
|
||||
@@ -15,7 +15,9 @@ using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Roles;
|
||||
@@ -377,30 +379,32 @@ public sealed class AdminSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erases a player from the round.
|
||||
/// This removes them and any trace of them from the round, deleting their
|
||||
/// chat messages and showing a popup to other players.
|
||||
/// Their items are dropped on the ground.
|
||||
/// </summary>
|
||||
public void Erase(ICommonSession player)
|
||||
{
|
||||
var entity = player.AttachedEntity;
|
||||
_chat.DeleteMessagesBy(player);
|
||||
|
||||
if (entity != null && !TerminatingOrDeleted(entity.Value))
|
||||
/// <summary>
|
||||
/// Erases a player from the round.
|
||||
/// This removes them and any trace of them from the round, deleting their
|
||||
/// chat messages and showing a popup to other players.
|
||||
/// Their items are dropped on the ground.
|
||||
/// </summary>
|
||||
public void Erase(NetUserId uid)
|
||||
{
|
||||
if (TryComp(entity.Value, out TransformComponent? transform))
|
||||
_chat.DeleteMessagesBy(uid);
|
||||
|
||||
if (!_minds.TryGetMind(uid, out var mindId, out var mind) || mind.OwnedEntity == null || TerminatingOrDeleted(mind.OwnedEntity.Value))
|
||||
return;
|
||||
|
||||
var entity = mind.OwnedEntity.Value;
|
||||
|
||||
if (TryComp(entity, out TransformComponent? transform))
|
||||
{
|
||||
var coordinates = _transform.GetMoverCoordinates(entity.Value, transform);
|
||||
var name = Identity.Entity(entity.Value, EntityManager);
|
||||
var coordinates = _transform.GetMoverCoordinates(entity, transform);
|
||||
var name = Identity.Entity(entity, EntityManager);
|
||||
_popup.PopupCoordinates(Loc.GetString("admin-erase-popup", ("user", name)), coordinates, PopupType.LargeCaution);
|
||||
var filter = Filter.Pvs(coordinates, 1, EntityManager, _playerManager);
|
||||
var audioParams = new AudioParams().WithVolume(3);
|
||||
_audio.PlayStatic("/Audio/Effects/pop_high.ogg", filter, coordinates, true, audioParams);
|
||||
}
|
||||
|
||||
foreach (var item in _inventory.GetHandOrInventoryEntities(entity.Value))
|
||||
foreach (var item in _inventory.GetHandOrInventoryEntities(entity))
|
||||
{
|
||||
if (TryComp(item, out PdaComponent? pda) &&
|
||||
TryComp(pda.ContainedId, out StationRecordKeyStorageComponent? keyStorage) &&
|
||||
@@ -424,30 +428,30 @@ public sealed class AdminSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
if (_inventory.TryGetContainerSlotEnumerator(entity.Value, out var enumerator))
|
||||
if (_inventory.TryGetContainerSlotEnumerator(entity, out var enumerator))
|
||||
{
|
||||
while (enumerator.NextItem(out var item, out var slot))
|
||||
{
|
||||
if (_inventory.TryUnequip(entity.Value, entity.Value, slot.Name, true, true))
|
||||
if (_inventory.TryUnequip(entity, entity, slot.Name, true, true))
|
||||
_physics.ApplyAngularImpulse(item, ThrowingSystem.ThrowAngularImpulse);
|
||||
}
|
||||
}
|
||||
|
||||
if (TryComp(entity.Value, out HandsComponent? hands))
|
||||
if (TryComp(entity, out HandsComponent? hands))
|
||||
{
|
||||
foreach (var hand in _hands.EnumerateHands(entity.Value, hands))
|
||||
foreach (var hand in _hands.EnumerateHands(entity, hands))
|
||||
{
|
||||
_hands.TryDrop(entity.Value, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands);
|
||||
_hands.TryDrop(entity, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands);
|
||||
}
|
||||
}
|
||||
|
||||
_minds.WipeMind(mindId, mind);
|
||||
QueueDel(entity);
|
||||
|
||||
if (_playerManager.TryGetSessionById(uid, out var session))
|
||||
_gameTicker.SpawnObserver(session);
|
||||
}
|
||||
|
||||
_minds.WipeMind(player);
|
||||
QueueDel(entity);
|
||||
|
||||
_gameTicker.SpawnObserver(player);
|
||||
}
|
||||
|
||||
private void OnSessionPlayTimeUpdated(ICommonSession session)
|
||||
{
|
||||
UpdatePlayerList(session);
|
||||
|
||||
@@ -34,11 +34,11 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Server.Player;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
|
||||
@@ -137,34 +137,6 @@ namespace Content.Server.Administration.Systems
|
||||
prayerVerb.Impact = LogImpact.Low;
|
||||
args.Verbs.Add(prayerVerb);
|
||||
|
||||
// Erase
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("admin-verbs-erase"),
|
||||
Message = Loc.GetString("admin-verbs-erase-description"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_adminSystem.Erase(targetActor.PlayerSession);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
ConfirmationPopup = true
|
||||
});
|
||||
|
||||
// Respawn
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("admin-player-actions-respawn"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
_console.ExecuteCommand(player, $"respawn {targetActor.PlayerSession.Name}");
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
// No logimpact as the command does it internally.
|
||||
});
|
||||
|
||||
// Spawn - Like respawn but on the spot.
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
@@ -225,6 +197,38 @@ namespace Content.Server.Administration.Systems
|
||||
});
|
||||
}
|
||||
|
||||
if (_mindSystem.TryGetMind(args.Target, out _, out var mind) && mind.UserId != null)
|
||||
{
|
||||
// Erase
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("admin-verbs-erase"),
|
||||
Message = Loc.GetString("admin-verbs-erase-description"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(
|
||||
new("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_adminSystem.Erase(mind.UserId.Value);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
ConfirmationPopup = true
|
||||
});
|
||||
|
||||
// Respawn
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("admin-player-actions-respawn"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
_console.ExecuteCommand(player, $"respawn \"{mind.UserId}\"");
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
// No logimpact as the command does it internally.
|
||||
});
|
||||
}
|
||||
|
||||
// Freeze
|
||||
var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
|
||||
var frozenAndMuted = frozenComp?.Muted ?? false;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
@@ -13,7 +14,7 @@ namespace Content.Server.Administration.Toolshed;
|
||||
[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class SolutionCommand : ToolshedCommand
|
||||
{
|
||||
private SolutionContainerSystem? _solutionContainer;
|
||||
private SharedSolutionContainerSystem? _solutionContainer;
|
||||
|
||||
[CommandImplementation("get")]
|
||||
public SolutionRef? Get(
|
||||
@@ -22,7 +23,7 @@ public sealed class SolutionCommand : ToolshedCommand
|
||||
[CommandArgument] ValueRef<string> name
|
||||
)
|
||||
{
|
||||
_solutionContainer ??= GetSys<SolutionContainerSystem>();
|
||||
_solutionContainer ??= GetSys<SharedSolutionContainerSystem>();
|
||||
|
||||
if (_solutionContainer.TryGetSolution(input, name.Evaluate(ctx)!, out var solution))
|
||||
return new SolutionRef(solution.Value);
|
||||
@@ -48,7 +49,7 @@ public sealed class SolutionCommand : ToolshedCommand
|
||||
[CommandArgument] ValueRef<FixedPoint2> amountRef
|
||||
)
|
||||
{
|
||||
_solutionContainer ??= GetSys<SolutionContainerSystem>();
|
||||
_solutionContainer ??= GetSys<SharedSolutionContainerSystem>();
|
||||
|
||||
var amount = amountRef.Evaluate(ctx);
|
||||
if (amount > 0)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.EUI;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -17,13 +18,13 @@ namespace Content.Server.Administration.UI
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
public readonly EntityUid Target;
|
||||
|
||||
public EditSolutionsEui(EntityUid entity)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_solutionContainerSystem = _entityManager.System<SolutionContainerSystem>();
|
||||
_solutionContainerSystem = _entityManager.System<SharedSolutionContainerSystem>();
|
||||
Target = entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Animals.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -25,7 +25,7 @@ internal sealed class UdderSystem : EntitySystem
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Animals.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Nutrition;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
@@ -9,7 +9,7 @@ using Robust.Shared.Timing;
|
||||
namespace Content.Server.Animals.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Gives ability to produce fiber reagents, produces endless if the
|
||||
/// Gives ability to produce fiber reagents, produces endless if the
|
||||
/// owner has no HungerComponent
|
||||
/// </summary>
|
||||
public sealed class WoolySystem : EntitySystem
|
||||
@@ -17,7 +17,7 @@ public sealed class WoolySystem : EntitySystem
|
||||
[Dependency] private readonly HungerSystem _hunger = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Anomaly;
|
||||
@@ -11,10 +10,7 @@ using Content.Shared.Physics;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Map;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
@@ -115,7 +111,7 @@ public sealed partial class AnomalySystem
|
||||
var valid = true;
|
||||
|
||||
// TODO: This should be using static lookup.
|
||||
foreach (var ent in gridComp.GetAnchoredEntities(tile))
|
||||
foreach (var ent in _mapSystem.GetAnchoredEntities(grid, gridComp, tile))
|
||||
{
|
||||
if (!physQuery.TryGetComponent(ent, out var body))
|
||||
continue;
|
||||
@@ -131,10 +127,10 @@ public sealed partial class AnomalySystem
|
||||
continue;
|
||||
|
||||
var pos = _mapSystem.GridTileToLocal(grid, gridComp, tile);
|
||||
var mapPos = pos.ToMap(EntityManager, _transform);
|
||||
var mapPos = _transform.ToMapCoordinates(pos);
|
||||
// don't spawn in AntiAnomalyZones
|
||||
var antiAnomalyZonesQueue = AllEntityQuery<AntiAnomalyZoneComponent, TransformComponent>();
|
||||
while (antiAnomalyZonesQueue.MoveNext(out var uid, out var zone, out var antiXform))
|
||||
while (antiAnomalyZonesQueue.MoveNext(out _, out var zone, out var antiXform))
|
||||
{
|
||||
if (antiXform.MapID != mapPos.MapId)
|
||||
continue;
|
||||
|
||||
61
Content.Server/Anomaly/Components/TechAnomalyComponent.cs
Normal file
61
Content.Server/Anomaly/Components/TechAnomalyComponent.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Content.Server.Anomaly.Effects;
|
||||
using Content.Shared.Destructible.Thresholds;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Anomaly.Components;
|
||||
|
||||
[RegisterComponent, AutoGenerateComponentPause, Access(typeof(TechAnomalySystem))]
|
||||
public sealed partial class TechAnomalyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// the distance at which random ports will bind to the anomaly. Scales with severity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public MinMax LinkRadius = new(5, 10);
|
||||
|
||||
/// <summary>
|
||||
/// the maximum number of entities with which an anomaly is associated during pulsing. Scales with severity
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public MinMax LinkCountPerPulse = new(2, 8);
|
||||
|
||||
/// <summary>
|
||||
/// Number of linkable pairs. when supercrit, the anomaly will link random devices in the radius to each other in pairs.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int LinkCountSupercritical = 30;
|
||||
|
||||
/// <summary>
|
||||
/// port activated by pulsation of the anomaly
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> PulsePort = "Pulse";
|
||||
|
||||
/// <summary>
|
||||
/// A port that activates every few seconds of an anomaly's lifetime
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> TimerPort = "Timer";
|
||||
|
||||
/// <summary>
|
||||
/// Chance of emag the device, when supercrit
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float EmagSupercritProbability = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// A prototype beam shot into devices when pulsed
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId LinkBeamProto = "AnomalyTechBeam";
|
||||
|
||||
/// <summary>
|
||||
/// time until the next activation of the timer ports
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextTimer = TimeSpan.Zero;
|
||||
|
||||
[DataField]
|
||||
public float TimerFrequency = 3f;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using System.Linq;
|
||||
@@ -16,7 +16,7 @@ namespace Content.Server.Anomaly.Effects;
|
||||
public sealed class InjectionAnomalySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
private EntityQuery<InjectableSolutionComponent> _injectableQuery;
|
||||
@@ -24,7 +24,7 @@ public sealed class InjectionAnomalySystem : EntitySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<InjectionAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||
SubscribeLocalEvent<InjectionAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical, before: new[] { typeof(SolutionContainerSystem) });
|
||||
SubscribeLocalEvent<InjectionAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical, before: new[] { typeof(SharedSolutionContainerSystem) });
|
||||
|
||||
_injectableQuery = GetEntityQuery<InjectableSolutionComponent>();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Content.Server.Anomaly.Effects;
|
||||
public sealed class PuddleCreateAnomalySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PuddleSystem _puddle = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Sprite;
|
||||
@@ -28,7 +28,7 @@ public sealed class ReagentProducerAnomalySystem : EntitySystem
|
||||
//Useful:
|
||||
//Those reagents that the players are hunting for. Very low percentage of loss.
|
||||
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly PointLightSystem _light = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
125
Content.Server/Anomaly/Effects/TechAnomalySystem.cs
Normal file
125
Content.Server/Anomaly/Effects/TechAnomalySystem.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.Beam;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
public sealed class TechAnomalySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DeviceLinkSystem _signal = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly BeamSystem _beam = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TechAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||
SubscribeLocalEvent<TechAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
|
||||
SubscribeLocalEvent<TechAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<TechAnomalyComponent, AnomalyComponent>();
|
||||
while (query.MoveNext(out var uid, out var tech, out var anom))
|
||||
{
|
||||
if (_timing.CurTime < tech.NextTimer)
|
||||
return;
|
||||
|
||||
tech.NextTimer = _timing.CurTime + TimeSpan.FromSeconds(tech.TimerFrequency * anom.Stability);
|
||||
|
||||
_signal.InvokePort(uid, tech.TimerPort);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStabilityChanged(Entity<TechAnomalyComponent> tech, ref AnomalyStabilityChangedEvent args)
|
||||
{
|
||||
var links = MathHelper.Lerp(tech.Comp.LinkCountPerPulse.Min, tech.Comp.LinkCountPerPulse.Max, args.Severity);
|
||||
CreateNewRandomLink(tech, (int)links);
|
||||
}
|
||||
|
||||
private void CreateNewRandomLink(Entity<TechAnomalyComponent> tech, int count)
|
||||
{
|
||||
if (!TryComp<AnomalyComponent>(tech, out var anomaly))
|
||||
return;
|
||||
if (!TryComp<DeviceLinkSourceComponent>(tech, out var sourceComp))
|
||||
return;
|
||||
|
||||
var range = MathHelper.Lerp(tech.Comp.LinkRadius.Min, tech.Comp.LinkRadius.Max, anomaly.Severity);
|
||||
|
||||
var devices = _lookup.GetEntitiesInRange<DeviceLinkSinkComponent>(Transform(tech).Coordinates, range);
|
||||
if (devices.Count < 1)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var device = _random.Pick(devices);
|
||||
CreateNewLink(tech, (tech, sourceComp), device);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateNewLink(Entity<TechAnomalyComponent> tech, Entity<DeviceLinkSourceComponent> source, Entity<DeviceLinkSinkComponent> target)
|
||||
{
|
||||
var sourcePort = _random.Pick(source.Comp.Ports);
|
||||
var sinkPort = _random.Pick(target.Comp.Ports);
|
||||
|
||||
_signal.SaveLinks(null, source, target,new()
|
||||
{
|
||||
(sourcePort, sinkPort),
|
||||
});
|
||||
_beam.TryCreateBeam(source, target, tech.Comp.LinkBeamProto);
|
||||
}
|
||||
|
||||
private void OnSupercritical(Entity<TechAnomalyComponent> tech, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
// We remove the component so that the anomaly does not bind itself to other devices before self destroy.
|
||||
RemComp<DeviceLinkSourceComponent>(tech);
|
||||
|
||||
var sources =
|
||||
_lookup.GetEntitiesInRange<DeviceLinkSourceComponent>(Transform(tech).Coordinates,
|
||||
tech.Comp.LinkRadius.Max);
|
||||
|
||||
var sinks =
|
||||
_lookup.GetEntitiesInRange<DeviceLinkSinkComponent>(Transform(tech).Coordinates,
|
||||
tech.Comp.LinkRadius.Max);
|
||||
|
||||
for (var i = 0; i < tech.Comp.LinkCountSupercritical; i++)
|
||||
{
|
||||
if (sources.Count < 1)
|
||||
return;
|
||||
|
||||
if (sinks.Count < 1)
|
||||
return;
|
||||
|
||||
var source = _random.Pick(sources);
|
||||
sources.Remove(source);
|
||||
|
||||
var sink = _random.Pick(sinks);
|
||||
sinks.Remove(sink);
|
||||
|
||||
if (_random.Prob(tech.Comp.EmagSupercritProbability))
|
||||
{
|
||||
_emag.DoEmagEffect(tech, source);
|
||||
_emag.DoEmagEffect(tech, sink);
|
||||
}
|
||||
|
||||
CreateNewLink(tech, source, sink);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPulse(Entity<TechAnomalyComponent> tech, ref AnomalyPulseEvent args)
|
||||
{
|
||||
_signal.InvokePort(tech, tech.Comp.PulsePort);
|
||||
}
|
||||
}
|
||||
@@ -40,15 +40,12 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IServerPreferencesManager _pref = default!;
|
||||
[Dependency] private readonly ActorSystem _actors = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly GhostRoleSystem _ghostRole = default!;
|
||||
[Dependency] private readonly JobSystem _jobs = default!;
|
||||
[Dependency] private readonly LoadoutSystem _loadout = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly RoleSystem _role = default!;
|
||||
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
|
||||
|
||||
348
Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs
Normal file
348
Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs
Normal file
@@ -0,0 +1,348 @@
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Consoles;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
private const float UpdateTime = 1.0f;
|
||||
|
||||
// Note: this data does not need to be saved
|
||||
private float _updateTimer = 1.0f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// Console events
|
||||
SubscribeLocalEvent<AtmosAlertsComputerComponent, ComponentInit>(OnConsoleInit);
|
||||
SubscribeLocalEvent<AtmosAlertsComputerComponent, EntParentChangedMessage>(OnConsoleParentChanged);
|
||||
SubscribeLocalEvent<AtmosAlertsComputerComponent, AtmosAlertsComputerFocusChangeMessage>(OnFocusChangedMessage);
|
||||
|
||||
// Grid events
|
||||
SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
|
||||
SubscribeLocalEvent<AtmosAlertsDeviceComponent, AnchorStateChangedEvent>(OnDeviceAnchorChanged);
|
||||
}
|
||||
|
||||
#region Event handling
|
||||
|
||||
private void OnConsoleInit(EntityUid uid, AtmosAlertsComputerComponent component, ComponentInit args)
|
||||
{
|
||||
InitalizeConsole(uid, component);
|
||||
}
|
||||
|
||||
private void OnConsoleParentChanged(EntityUid uid, AtmosAlertsComputerComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
InitalizeConsole(uid, component);
|
||||
}
|
||||
|
||||
private void OnFocusChangedMessage(EntityUid uid, AtmosAlertsComputerComponent component, AtmosAlertsComputerFocusChangeMessage args)
|
||||
{
|
||||
component.FocusDevice = args.FocusDevice;
|
||||
}
|
||||
|
||||
private void OnGridSplit(ref GridSplitEvent args)
|
||||
{
|
||||
// Collect grids
|
||||
var allGrids = args.NewGrids.ToList();
|
||||
|
||||
if (!allGrids.Contains(args.Grid))
|
||||
allGrids.Add(args.Grid);
|
||||
|
||||
// Update atmos monitoring consoles that stand upon an updated grid
|
||||
var query = AllEntityQuery<AtmosAlertsComputerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
|
||||
{
|
||||
if (entXform.GridUid == null)
|
||||
continue;
|
||||
|
||||
if (!allGrids.Contains(entXform.GridUid.Value))
|
||||
continue;
|
||||
|
||||
InitalizeConsole(ent, entConsole);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent component, AnchorStateChangedEvent args)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
var gridUid = xform.GridUid;
|
||||
|
||||
if (gridUid == null)
|
||||
return;
|
||||
|
||||
if (!TryGetAtmosDeviceNavMapData(uid, component, xform, gridUid.Value, out var data))
|
||||
return;
|
||||
|
||||
var netEntity = EntityManager.GetNetEntity(uid);
|
||||
|
||||
var query = AllEntityQuery<AtmosAlertsComputerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
|
||||
{
|
||||
if (gridUid != entXform.GridUid)
|
||||
continue;
|
||||
|
||||
if (args.Anchored)
|
||||
entConsole.AtmosDevices.Add(data.Value);
|
||||
|
||||
else if (!args.Anchored)
|
||||
entConsole.AtmosDevices.RemoveWhere(x => x.NetEntity == netEntity);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_updateTimer += frameTime;
|
||||
|
||||
if (_updateTimer >= UpdateTime)
|
||||
{
|
||||
_updateTimer -= UpdateTime;
|
||||
|
||||
// Keep a list of UI entries for each gridUid, in case multiple consoles stand on the same grid
|
||||
var airAlarmEntriesForEachGrid = new Dictionary<EntityUid, AtmosAlertsComputerEntry[]>();
|
||||
var fireAlarmEntriesForEachGrid = new Dictionary<EntityUid, AtmosAlertsComputerEntry[]>();
|
||||
|
||||
var query = AllEntityQuery<AtmosAlertsComputerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
|
||||
{
|
||||
if (entXform?.GridUid == null)
|
||||
continue;
|
||||
|
||||
// Make a list of alarm state data for all the air and fire alarms on the grid
|
||||
if (!airAlarmEntriesForEachGrid.TryGetValue(entXform.GridUid.Value, out var airAlarmEntries))
|
||||
{
|
||||
airAlarmEntries = GetAlarmStateData(entXform.GridUid.Value, AtmosAlertsComputerGroup.AirAlarm).ToArray();
|
||||
airAlarmEntriesForEachGrid[entXform.GridUid.Value] = airAlarmEntries;
|
||||
}
|
||||
|
||||
if (!fireAlarmEntriesForEachGrid.TryGetValue(entXform.GridUid.Value, out var fireAlarmEntries))
|
||||
{
|
||||
fireAlarmEntries = GetAlarmStateData(entXform.GridUid.Value, AtmosAlertsComputerGroup.FireAlarm).ToArray();
|
||||
fireAlarmEntriesForEachGrid[entXform.GridUid.Value] = fireAlarmEntries;
|
||||
}
|
||||
|
||||
// Determine the highest level of alert for the console (based on non-silenced alarms)
|
||||
var highestAlert = AtmosAlarmType.Invalid;
|
||||
|
||||
foreach (var entry in airAlarmEntries)
|
||||
{
|
||||
if (entry.AlarmState > highestAlert && !entConsole.SilencedDevices.Contains(entry.NetEntity))
|
||||
highestAlert = entry.AlarmState;
|
||||
}
|
||||
|
||||
foreach (var entry in fireAlarmEntries)
|
||||
{
|
||||
if (entry.AlarmState > highestAlert && !entConsole.SilencedDevices.Contains(entry.NetEntity))
|
||||
highestAlert = entry.AlarmState;
|
||||
}
|
||||
|
||||
// Update the appearance of the console based on the highest recorded level of alert
|
||||
if (TryComp<AppearanceComponent>(ent, out var entAppearance))
|
||||
_appearance.SetData(ent, AtmosAlertsComputerVisuals.ComputerLayerScreen, (int) highestAlert, entAppearance);
|
||||
|
||||
// If the console UI is open, send UI data to each subscribed session
|
||||
UpdateUIState(ent, airAlarmEntries, fireAlarmEntries, entConsole, entXform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateUIState
|
||||
(EntityUid uid,
|
||||
AtmosAlertsComputerEntry[] airAlarmStateData,
|
||||
AtmosAlertsComputerEntry[] fireAlarmStateData,
|
||||
AtmosAlertsComputerComponent component,
|
||||
TransformComponent xform)
|
||||
{
|
||||
if (!_userInterfaceSystem.IsUiOpen(uid, AtmosAlertsComputerUiKey.Key))
|
||||
return;
|
||||
|
||||
var gridUid = xform.GridUid!.Value;
|
||||
|
||||
if (!HasComp<MapGridComponent>(gridUid))
|
||||
return;
|
||||
|
||||
// The grid must have a NavMapComponent to visualize the map in the UI
|
||||
EnsureComp<NavMapComponent>(gridUid);
|
||||
|
||||
// Gathering remaining data to be send to the client
|
||||
var focusAlarmData = GetFocusAlarmData(uid, GetEntity(component.FocusDevice), gridUid);
|
||||
|
||||
// Set the UI state
|
||||
_userInterfaceSystem.SetUiState(uid, AtmosAlertsComputerUiKey.Key,
|
||||
new AtmosAlertsComputerBoundInterfaceState(airAlarmStateData, fireAlarmStateData, focusAlarmData));
|
||||
}
|
||||
|
||||
private List<AtmosAlertsComputerEntry> GetAlarmStateData(EntityUid gridUid, AtmosAlertsComputerGroup group)
|
||||
{
|
||||
var alarmStateData = new List<AtmosAlertsComputerEntry>();
|
||||
|
||||
var queryAlarms = AllEntityQuery<AtmosAlertsDeviceComponent, AtmosAlarmableComponent, DeviceNetworkComponent, TransformComponent>();
|
||||
while (queryAlarms.MoveNext(out var ent, out var entDevice, out var entAtmosAlarmable, out var entDeviceNetwork, out var entXform))
|
||||
{
|
||||
if (entXform.GridUid != gridUid)
|
||||
continue;
|
||||
|
||||
if (!entXform.Anchored)
|
||||
continue;
|
||||
|
||||
if (entDevice.Group != group)
|
||||
continue;
|
||||
|
||||
// If emagged, change the alarm type to normal
|
||||
var alarmState = (entAtmosAlarmable.LastAlarmState == AtmosAlarmType.Emagged) ? AtmosAlarmType.Normal : entAtmosAlarmable.LastAlarmState;
|
||||
|
||||
// Unpowered alarms can't sound
|
||||
if (TryComp<ApcPowerReceiverComponent>(ent, out var entAPCPower) && !entAPCPower.Powered)
|
||||
alarmState = AtmosAlarmType.Invalid;
|
||||
|
||||
var entry = new AtmosAlertsComputerEntry
|
||||
(GetNetEntity(ent),
|
||||
GetNetCoordinates(entXform.Coordinates),
|
||||
entDevice.Group,
|
||||
alarmState,
|
||||
MetaData(ent).EntityName,
|
||||
entDeviceNetwork.Address);
|
||||
|
||||
alarmStateData.Add(entry);
|
||||
}
|
||||
|
||||
return alarmStateData;
|
||||
}
|
||||
|
||||
private AtmosAlertsFocusDeviceData? GetFocusAlarmData(EntityUid uid, EntityUid? focusDevice, EntityUid gridUid)
|
||||
{
|
||||
if (focusDevice == null)
|
||||
return null;
|
||||
|
||||
var focusDeviceXform = Transform(focusDevice.Value);
|
||||
|
||||
if (!focusDeviceXform.Anchored ||
|
||||
focusDeviceXform.GridUid != gridUid ||
|
||||
!TryComp<AirAlarmComponent>(focusDevice.Value, out var focusDeviceAirAlarm))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Force update the sensors attached to the alarm
|
||||
if (!_userInterfaceSystem.IsUiOpen(focusDevice.Value, SharedAirAlarmInterfaceKey.Key))
|
||||
{
|
||||
_atmosDevNet.Register(focusDevice.Value, null);
|
||||
_atmosDevNet.Sync(focusDevice.Value, null);
|
||||
|
||||
foreach ((var address, var _) in focusDeviceAirAlarm.SensorData)
|
||||
_atmosDevNet.Register(uid, null);
|
||||
}
|
||||
|
||||
// Get the sensor data
|
||||
var temperatureData = (_airAlarmSystem.CalculateTemperatureAverage(focusDeviceAirAlarm), AtmosAlarmType.Normal);
|
||||
var pressureData = (_airAlarmSystem.CalculatePressureAverage(focusDeviceAirAlarm), AtmosAlarmType.Normal);
|
||||
var gasData = new Dictionary<Gas, (float, float, AtmosAlarmType)>();
|
||||
|
||||
foreach ((var address, var sensorData) in focusDeviceAirAlarm.SensorData)
|
||||
{
|
||||
if (sensorData.TemperatureThreshold.CheckThreshold(sensorData.Temperature, out var temperatureState) &&
|
||||
(int) temperatureState > (int) temperatureData.Item2)
|
||||
{
|
||||
temperatureData = (temperatureData.Item1, temperatureState);
|
||||
}
|
||||
|
||||
if (sensorData.PressureThreshold.CheckThreshold(sensorData.Pressure, out var pressureState) &&
|
||||
(int) pressureState > (int) pressureData.Item2)
|
||||
{
|
||||
pressureData = (pressureData.Item1, pressureState);
|
||||
}
|
||||
|
||||
if (focusDeviceAirAlarm.SensorData.Sum(g => g.Value.TotalMoles) > 1e-8)
|
||||
{
|
||||
foreach ((var gas, var threshold) in sensorData.GasThresholds)
|
||||
{
|
||||
if (!gasData.ContainsKey(gas))
|
||||
{
|
||||
float mol = _airAlarmSystem.CalculateGasMolarConcentrationAverage(focusDeviceAirAlarm, gas, out var percentage);
|
||||
|
||||
if (mol < 1e-8)
|
||||
continue;
|
||||
|
||||
gasData[gas] = (mol, percentage, AtmosAlarmType.Normal);
|
||||
}
|
||||
|
||||
if (threshold.CheckThreshold(gasData[gas].Item2, out var gasState) &&
|
||||
(int) gasState > (int) gasData[gas].Item3)
|
||||
{
|
||||
gasData[gas] = (gasData[gas].Item1, gasData[gas].Item2, gasState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new AtmosAlertsFocusDeviceData(GetNetEntity(focusDevice.Value), temperatureData, pressureData, gasData);
|
||||
}
|
||||
|
||||
private HashSet<AtmosAlertsDeviceNavMapData> GetAllAtmosDeviceNavMapData(EntityUid gridUid)
|
||||
{
|
||||
var atmosDeviceNavMapData = new HashSet<AtmosAlertsDeviceNavMapData>();
|
||||
|
||||
var query = AllEntityQuery<AtmosAlertsDeviceComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var ent, out var entComponent, out var entXform))
|
||||
{
|
||||
if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, gridUid, out var data))
|
||||
atmosDeviceNavMapData.Add(data.Value);
|
||||
}
|
||||
|
||||
return atmosDeviceNavMapData;
|
||||
}
|
||||
|
||||
private bool TryGetAtmosDeviceNavMapData
|
||||
(EntityUid uid,
|
||||
AtmosAlertsDeviceComponent component,
|
||||
TransformComponent xform,
|
||||
EntityUid gridUid,
|
||||
[NotNullWhen(true)] out AtmosAlertsDeviceNavMapData? output)
|
||||
{
|
||||
output = null;
|
||||
|
||||
if (xform.GridUid != gridUid)
|
||||
return false;
|
||||
|
||||
if (!xform.Anchored)
|
||||
return false;
|
||||
|
||||
output = new AtmosAlertsDeviceNavMapData(GetNetEntity(uid), GetNetCoordinates(xform.Coordinates), component.Group);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void InitalizeConsole(EntityUid uid, AtmosAlertsComputerComponent component)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (xform.GridUid == null)
|
||||
return;
|
||||
|
||||
var grid = xform.GridUid.Value;
|
||||
component.AtmosDevices = GetAllAtmosDeviceNavMapData(grid);
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
@@ -297,7 +297,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
else
|
||||
{
|
||||
flammable.OnFire = ignite;
|
||||
flammable.OnFire |= ignite;
|
||||
UpdateAppearance(uid, flammable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
private void OnExamined(EntityUid uid, GasTankComponent component, ExaminedEvent args)
|
||||
{
|
||||
using(args.PushGroup(nameof(GasTankComponent)));
|
||||
using var _ = args.PushGroup(nameof(GasTankComponent));
|
||||
if (args.IsInDetailsRange)
|
||||
args.PushMarkup(Loc.GetString("comp-gas-tank-examine", ("pressure", Math.Round(component.Air?.Pressure ?? 0))));
|
||||
if (component.IsConnected)
|
||||
@@ -348,7 +348,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (component.Integrity <= 0)
|
||||
{
|
||||
var environment = _atmosphereSystem.GetContainingMixture(owner, false, true);
|
||||
if(environment != null)
|
||||
if (environment != null)
|
||||
_atmosphereSystem.Merge(environment, component.Air);
|
||||
|
||||
_audioSys.PlayPvs(component.RuptureSound, Transform(owner).Coordinates, AudioParams.Default.WithVariation(0.125f));
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
@@ -22,6 +21,7 @@ using Content.Shared.Power;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
@@ -582,6 +582,20 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
? alarm.SensorData.Values.Select(v => v.Temperature).Average()
|
||||
: 0f;
|
||||
}
|
||||
public float CalculateGasMolarConcentrationAverage(AirAlarmComponent alarm, Gas gas, out float percentage)
|
||||
{
|
||||
percentage = 0f;
|
||||
|
||||
var data = alarm.SensorData.Values.SelectMany(v => v.Gases.Where(g => g.Key == gas));
|
||||
|
||||
if (data.Count() == 0)
|
||||
return 0f;
|
||||
|
||||
var averageMol = data.Select(kvp => kvp.Value).Average();
|
||||
percentage = data.Select(kvp => kvp.Value).Sum() / alarm.SensorData.Values.Select(v => v.TotalMoles).Sum();
|
||||
|
||||
return averageMol;
|
||||
}
|
||||
|
||||
public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetworkComponent? devNet = null, AtmosAlarmableComponent? alarmable = null)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.EntityEffects.Effects;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Forensics;
|
||||
@@ -37,7 +36,7 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedDrunkSystem _drunkSystem = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedStutteringSystem _stutteringSystem = default!;
|
||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly ForensicsSystem _forensicsSystem = default!;
|
||||
@@ -178,9 +177,16 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
|
||||
private void OnComponentInit(Entity<BloodstreamComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
var chemicalSolution = _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.ChemicalSolutionName);
|
||||
var bloodSolution = _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.BloodSolutionName);
|
||||
var tempSolution = _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.BloodTemporarySolutionName);
|
||||
if (!_solutionContainerSystem.EnsureSolution(entity.Owner,
|
||||
entity.Comp.ChemicalSolutionName,
|
||||
out var chemicalSolution) ||
|
||||
!_solutionContainerSystem.EnsureSolution(entity.Owner,
|
||||
entity.Comp.BloodSolutionName,
|
||||
out var bloodSolution) ||
|
||||
!_solutionContainerSystem.EnsureSolution(entity.Owner,
|
||||
entity.Comp.BloodTemporarySolutionName,
|
||||
out var tempSolution))
|
||||
return;
|
||||
|
||||
chemicalSolution.MaxVolume = entity.Comp.ChemicalMaxVolume;
|
||||
bloodSolution.MaxVolume = entity.Comp.BloodMaxVolume;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Clothing;
|
||||
@@ -13,7 +13,7 @@ public sealed class LungSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private readonly InternalsSystem _internals = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public static string LungSolutionName = "Lung";
|
||||
@@ -50,9 +50,11 @@ public sealed class LungSystem : EntitySystem
|
||||
|
||||
private void OnComponentInit(Entity<LungComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
var solution = _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName);
|
||||
solution.MaxVolume = 100.0f;
|
||||
solution.CanReact = false; // No dexalin lungs
|
||||
if (_solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out var solution))
|
||||
{
|
||||
solution.MaxVolume = 100.0f;
|
||||
solution.CanReact = false; // No dexalin lungs
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMaskToggled(Entity<BreathToolComponent> ent, ref ItemMaskToggledEvent args)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
@@ -24,7 +24,7 @@ namespace Content.Server.Body.Systems
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
private EntityQuery<OrganComponent> _organQuery;
|
||||
private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
|
||||
@@ -56,11 +56,11 @@ namespace Content.Server.Body.Systems
|
||||
{
|
||||
if (!entity.Comp.SolutionOnBody)
|
||||
{
|
||||
_solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName);
|
||||
_solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _);
|
||||
}
|
||||
else if (_organQuery.CompOrNull(entity)?.Body is { } body)
|
||||
{
|
||||
_solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName);
|
||||
_solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.EntityEffects.EffectConditions;
|
||||
using Content.Server.EntityEffects.Effects;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Body.Components;
|
||||
@@ -33,7 +33,7 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
[Dependency] private readonly LungSystem _lungSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
|
||||
private static readonly ProtoId<MetabolismGroupPrototype> GasId = new("Gas");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
@@ -11,7 +11,7 @@ namespace Content.Server.Body.Systems
|
||||
public sealed class StomachSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
public const string DefaultSolutionName = "stomach";
|
||||
|
||||
|
||||
@@ -10,7 +10,11 @@ public sealed partial class BotanySystem
|
||||
if (!TryGetSeed(produce, out var seed))
|
||||
return;
|
||||
|
||||
var solutionContainer = _solutionContainerSystem.EnsureSolution(uid, produce.SolutionName, FixedPoint2.Zero, out _);
|
||||
if (!_solutionContainerSystem.EnsureSolution(uid,
|
||||
produce.SolutionName,
|
||||
out var solutionContainer,
|
||||
FixedPoint2.Zero))
|
||||
return;
|
||||
|
||||
solutionContainer.RemoveAllSolution();
|
||||
foreach (var (chem, quantity) in seed.Chemicals)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Botany;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
@@ -31,7 +31,7 @@ public sealed partial class BotanySystem : EntitySystem
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly FixtureSystem _fixtureSystem = default!;
|
||||
[Dependency] private readonly CollisionWakeSystem _colWakeSystem = default!;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Fluids.Components;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Botany;
|
||||
using Content.Shared.Burial.Components;
|
||||
@@ -40,7 +40,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user