upstream
This commit is contained in:
commit
aa38c32b65
1145 changed files with 122593 additions and 44439 deletions
2
.github/workflows/update-credits.yml
vendored
2
.github/workflows/update-credits.yml
vendored
|
|
@ -19,6 +19,8 @@ jobs:
|
|||
|
||||
- name: Get this week's Contributors
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
run: Tools/dump_github_contributors.ps1 > Resources/Credits/GitHub.txt
|
||||
|
||||
# TODO
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -144,13 +144,13 @@ public sealed partial class ChangelogTab : Control
|
|||
Margin = new Thickness(6, 0, 0, 0),
|
||||
};
|
||||
authorLabel.SetMessage(
|
||||
FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", changeEntry.Author))));
|
||||
FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", changeEntry.Author))));
|
||||
ChangelogBody.AddChild(authorLabel);
|
||||
|
||||
foreach (var change in changeEntry.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";
|
||||
|
|
|
|||
|
|
@ -53,7 +53,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,7 +1,12 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
|
@ -12,13 +17,8 @@ using Robust.Client.UserInterface.Controls;
|
|||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using static Content.Shared.Interaction.SharedInteractionSystem;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
|
||||
namespace Content.Client.Examine
|
||||
|
|
@ -35,7 +35,6 @@ namespace Content.Client.Examine
|
|||
|
||||
private EntityUid _examinedEntity;
|
||||
private EntityUid _lastExaminedEntity;
|
||||
private EntityUid _playerEntity;
|
||||
private Popup? _examineTooltipOpen;
|
||||
private ScreenCoordinates _popupPos;
|
||||
private CancellationTokenSource? _requestCancelTokenSource;
|
||||
|
|
@ -74,9 +73,9 @@ namespace Content.Client.Examine
|
|||
public override void Update(float frameTime)
|
||||
{
|
||||
if (_examineTooltipOpen is not {Visible: true}) return;
|
||||
if (!_examinedEntity.Valid || !_playerEntity.Valid) return;
|
||||
if (!_examinedEntity.Valid || _playerManager.LocalEntity is not { } player) return;
|
||||
|
||||
if (!CanExamine(_playerEntity, _examinedEntity))
|
||||
if (!CanExamine(player, _examinedEntity))
|
||||
CloseTooltip();
|
||||
}
|
||||
|
||||
|
|
@ -114,9 +113,8 @@ namespace Content.Client.Examine
|
|||
return false;
|
||||
}
|
||||
|
||||
_playerEntity = _playerManager.LocalEntity ?? default;
|
||||
|
||||
if (_playerEntity == default || !CanExamine(_playerEntity, entity))
|
||||
if (_playerManager.LocalEntity is not { } player ||
|
||||
!CanExamine(player, entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,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)
|
||||
|
|
|
|||
45
Content.Client/Guidebook/GuidebookDataSystem.cs
Normal file
45
Content.Client/Guidebook/GuidebookDataSystem.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Client.Guidebook;
|
||||
|
||||
/// <summary>
|
||||
/// Client system for storing and retrieving values extracted from entity prototypes
|
||||
/// for display in the guidebook (<see cref="RichText.ProtodataTag"/>).
|
||||
/// Requests data from the server on <see cref="Initialize"/>.
|
||||
/// Can also be pushed new data when the server reloads prototypes.
|
||||
/// </summary>
|
||||
public sealed class GuidebookDataSystem : EntitySystem
|
||||
{
|
||||
private GuidebookData? _data;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<UpdateGuidebookDataEvent>(OnServerUpdated);
|
||||
|
||||
// Request data from the server
|
||||
RaiseNetworkEvent(new RequestGuidebookDataEvent());
|
||||
}
|
||||
|
||||
private void OnServerUpdated(UpdateGuidebookDataEvent args)
|
||||
{
|
||||
// Got new data from the server, either in response to our request, or because prototypes reloaded on the server
|
||||
_data = args.Data;
|
||||
_data.Freeze();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve a value using the given identifiers.
|
||||
/// See <see cref="GuidebookData.TryGetValue"/> for more information.
|
||||
/// </summary>
|
||||
public bool TryGetValue(string prototype, string component, string field, out object? value)
|
||||
{
|
||||
if (_data == null)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
return _data.TryGetValue(prototype, component, field, out value);
|
||||
}
|
||||
}
|
||||
49
Content.Client/Guidebook/Richtext/ProtodataTag.cs
Normal file
49
Content.Client/Guidebook/Richtext/ProtodataTag.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System.Globalization;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Guidebook.RichText;
|
||||
|
||||
/// <summary>
|
||||
/// RichText tag that can display values extracted from entity prototypes.
|
||||
/// In order to be accessed by this tag, the desired field/property must
|
||||
/// be tagged with <see cref="Shared.Guidebook.GuidebookDataAttribute"/>.
|
||||
/// </summary>
|
||||
public sealed class ProtodataTag : IMarkupTag
|
||||
{
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public string Name => "protodata";
|
||||
private ISawmill Log => _log ??= _logMan.GetSawmill("protodata_tag");
|
||||
private ISawmill? _log;
|
||||
|
||||
public string TextBefore(MarkupNode node)
|
||||
{
|
||||
// Do nothing with an empty tag
|
||||
if (!node.Value.TryGetString(out var prototype))
|
||||
return string.Empty;
|
||||
|
||||
if (!node.Attributes.TryGetValue("comp", out var component))
|
||||
return string.Empty;
|
||||
if (!node.Attributes.TryGetValue("member", out var member))
|
||||
return string.Empty;
|
||||
node.Attributes.TryGetValue("format", out var format);
|
||||
|
||||
var guidebookData = _entMan.System<GuidebookDataSystem>();
|
||||
|
||||
// Try to get the value
|
||||
if (!guidebookData.TryGetValue(prototype, component.StringValue!, member.StringValue!, out var value))
|
||||
{
|
||||
Log.Error($"Failed to find protodata for {component}.{member} in {prototype}");
|
||||
return "???";
|
||||
}
|
||||
|
||||
// If we have a format string and a formattable value, format it as requested
|
||||
if (!string.IsNullOrEmpty(format.StringValue) && value is IFormattable formattable)
|
||||
return formattable.ToString(format.StringValue, CultureInfo.CurrentCulture);
|
||||
|
||||
// No format string given, so just use default ToString
|
||||
return value?.ToString() ?? "NULL";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -47,8 +47,10 @@
|
|||
</Control>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="ButtonSaveDraft" SetHeight="32" SetWidth="85"
|
||||
StyleClasses="OpenRight" Text="{Loc news-write-ui-save-text}"/>
|
||||
<Button Name="ButtonPreview" SetHeight="32" SetWidth="85"
|
||||
StyleClasses="OpenRight" Text="{Loc news-write-ui-preview-text}"/>
|
||||
StyleClasses="OpenBoth" Text="{Loc news-write-ui-preview-text}"/>
|
||||
<Button Name="ButtonPublish" SetHeight="32" SetWidth="85" Text="{Loc news-write-ui-publish-text}" Access="Public"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Content.Client.MassMedia.Ui;
|
|||
public sealed partial class ArticleEditorPanel : Control
|
||||
{
|
||||
public event Action? PublishButtonPressed;
|
||||
public event Action<string, string>? ArticleDraftUpdated;
|
||||
|
||||
private bool _preview;
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ public sealed partial class ArticleEditorPanel : Control
|
|||
ButtonPreview.OnPressed += OnPreview;
|
||||
ButtonCancel.OnPressed += OnCancel;
|
||||
ButtonPublish.OnPressed += OnPublish;
|
||||
ButtonSaveDraft.OnPressed += OnDraftSaved;
|
||||
|
||||
TitleField.OnTextChanged += args => OnTextChanged(args.Text.Length, args.Control, SharedNewsSystem.MaxTitleLength);
|
||||
ContentField.OnTextChanged += args => OnTextChanged(Rope.CalcTotalLength(args.TextRope), args.Control, SharedNewsSystem.MaxContentLength);
|
||||
|
|
@ -68,6 +70,9 @@ public sealed partial class ArticleEditorPanel : Control
|
|||
ButtonPublish.Disabled = false;
|
||||
ButtonPreview.Disabled = false;
|
||||
}
|
||||
|
||||
// save draft regardless; they can edit down the length later
|
||||
ArticleDraftUpdated?.Invoke(TitleField.Text, Rope.Collapse(ContentField.TextRope));
|
||||
}
|
||||
|
||||
private void OnPreview(BaseButton.ButtonEventArgs eventArgs)
|
||||
|
|
@ -92,6 +97,12 @@ public sealed partial class ArticleEditorPanel : Control
|
|||
Visible = false;
|
||||
}
|
||||
|
||||
private void OnDraftSaved(BaseButton.ButtonEventArgs eventArgs)
|
||||
{
|
||||
ArticleDraftUpdated?.Invoke(TitleField.Text, Rope.Collapse(ContentField.TextRope));
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_preview = false;
|
||||
|
|
@ -100,6 +111,7 @@ public sealed partial class ArticleEditorPanel : Control
|
|||
PreviewLabel.SetMarkup("");
|
||||
TitleField.Text = "";
|
||||
ContentField.TextRope = Rope.Leaf.Empty;
|
||||
ArticleDraftUpdated?.Invoke(string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ public sealed class NewsWriterBoundUserInterface : BoundUserInterface
|
|||
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;
|
||||
_menu.DeleteButtonPressed += OnDeleteButtonPressed;
|
||||
|
||||
_menu.CreateButtonPressed += OnCreateButtonPressed;
|
||||
_menu.ArticleEditorPanel.ArticleDraftUpdated += OnArticleDraftUpdated;
|
||||
|
||||
SendMessage(new NewsWriterArticlesRequestMessage());
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +37,7 @@ public sealed class NewsWriterBoundUserInterface : BoundUserInterface
|
|||
if (state is not NewsWriterBoundUserInterfaceState cast)
|
||||
return;
|
||||
|
||||
_menu?.UpdateUI(cast.Articles, cast.PublishEnabled, cast.NextPublish);
|
||||
_menu?.UpdateUI(cast.Articles, cast.PublishEnabled, cast.NextPublish, cast.DraftTitle, cast.DraftContent);
|
||||
}
|
||||
|
||||
private void OnPublishButtonPressed()
|
||||
|
|
@ -67,4 +70,14 @@ public sealed class NewsWriterBoundUserInterface : BoundUserInterface
|
|||
|
||||
SendMessage(new NewsWriterDeleteMessage(articleNum));
|
||||
}
|
||||
|
||||
private void OnCreateButtonPressed()
|
||||
{
|
||||
SendMessage(new NewsWriterRequestDraftMessage());
|
||||
}
|
||||
|
||||
private void OnArticleDraftUpdated(string title, string content)
|
||||
{
|
||||
SendMessage(new NewsWriterSaveDraftMessage(title, content));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Robust.Client.UserInterface.XAML;
|
|||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.MassMedia.Ui;
|
||||
|
||||
|
|
@ -16,6 +17,8 @@ public sealed partial class NewsWriterMenu : FancyWindow
|
|||
|
||||
public event Action<int>? DeleteButtonPressed;
|
||||
|
||||
public event Action? CreateButtonPressed;
|
||||
|
||||
public NewsWriterMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
|
@ -31,7 +34,7 @@ public sealed partial class NewsWriterMenu : FancyWindow
|
|||
ButtonCreate.OnPressed += OnCreate;
|
||||
}
|
||||
|
||||
public void UpdateUI(NewsArticle[] articles, bool publishEnabled, TimeSpan nextPublish)
|
||||
public void UpdateUI(NewsArticle[] articles, bool publishEnabled, TimeSpan nextPublish, string draftTitle, string draftContent)
|
||||
{
|
||||
ArticlesContainer.Children.Clear();
|
||||
ArticleCount.Text = Loc.GetString("news-write-ui-article-count-text", ("count", articles.Length));
|
||||
|
|
@ -54,6 +57,9 @@ public sealed partial class NewsWriterMenu : FancyWindow
|
|||
|
||||
ButtonCreate.Disabled = !publishEnabled;
|
||||
_nextPublish = nextPublish;
|
||||
|
||||
ArticleEditorPanel.TitleField.Text = draftTitle;
|
||||
ArticleEditorPanel.ContentField.TextRope = new Rope.Leaf(draftContent);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
|
|
@ -93,5 +99,6 @@ public sealed partial class NewsWriterMenu : FancyWindow
|
|||
private void OnCreate(BaseButton.ButtonEventArgs buttonEventArgs)
|
||||
{
|
||||
ArticleEditorPanel.Visible = true;
|
||||
CreateButtonPressed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -107,7 +107,7 @@ namespace Content.Client.Nuke
|
|||
FirstStatusLabel.Text = firstMsg;
|
||||
SecondStatusLabel.Text = secondMsg;
|
||||
|
||||
EjectButton.Disabled = !state.DiskInserted || state.Status == NukeStatus.ARMED;
|
||||
EjectButton.Disabled = !state.DiskInserted || state.Status == NukeStatus.ARMED || !state.IsAnchored;
|
||||
AnchorButton.Disabled = state.Status == NukeStatus.ARMED;
|
||||
AnchorButton.Pressed = state.IsAnchored;
|
||||
ArmButton.Disabled = !state.AllowArm || !state.IsAnchored;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -4,18 +4,20 @@ using Content.Shared.Containers.ItemSlots;
|
|||
using Content.Shared.PDA;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.PDA
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PdaBoundUserInterface : CartridgeLoaderBoundUserInterface
|
||||
{
|
||||
private readonly PdaSystem _pdaSystem;
|
||||
|
||||
[ViewVariables]
|
||||
private PdaMenu? _menu;
|
||||
|
||||
public PdaBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_pdaSystem = EntMan.System<PdaSystem>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
|
|
@ -92,7 +94,13 @@ namespace Content.Client.PDA
|
|||
if (state is not PdaUpdateState updateState)
|
||||
return;
|
||||
|
||||
_menu?.UpdateState(updateState);
|
||||
if (_menu == null)
|
||||
{
|
||||
_pdaSystem.Log.Error("PDA state received before menu was created.");
|
||||
return;
|
||||
}
|
||||
|
||||
_menu.UpdateState(updateState);
|
||||
}
|
||||
|
||||
protected override void AttachCartridgeUI(Control cartridgeUIFragment, string? title)
|
||||
|
|
|
|||
|
|
@ -73,14 +73,17 @@
|
|||
Description="{Loc 'comp-pda-ui-ringtone-button-description'}"/>
|
||||
<pda:PdaSettingsButton Name="ActivateMusicButton"
|
||||
Access="Public"
|
||||
Visible="False"
|
||||
Text="{Loc 'pda-bound-user-interface-music-button'}"
|
||||
Description="{Loc 'pda-bound-user-interface-music-button-description'}"/>
|
||||
<pda:PdaSettingsButton Name="ShowUplinkButton"
|
||||
Access="Public"
|
||||
Visible="False"
|
||||
Text="{Loc 'pda-bound-user-interface-show-uplink-title'}"
|
||||
Description="{Loc 'pda-bound-user-interface-show-uplink-description'}"/>
|
||||
<pda:PdaSettingsButton Name="LockUplinkButton"
|
||||
Access="Public"
|
||||
Visible="False"
|
||||
Text="{Loc 'pda-bound-user-interface-lock-uplink-title'}"
|
||||
Description="{Loc 'pda-bound-user-interface-lock-uplink-description'}"/>
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -8,132 +8,131 @@ using Robust.Shared.Physics.Components;
|
|||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Physics.Controllers
|
||||
namespace Content.Client.Physics.Controllers;
|
||||
|
||||
public sealed class MoverController : SharedMoverController
|
||||
{
|
||||
public sealed class MoverController : SharedMoverController
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RelayInputMoverComponent, LocalPlayerAttachedEvent>(OnRelayPlayerAttached);
|
||||
SubscribeLocalEvent<RelayInputMoverComponent, LocalPlayerDetachedEvent>(OnRelayPlayerDetached);
|
||||
SubscribeLocalEvent<InputMoverComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<InputMoverComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
public override void Initialize()
|
||||
SubscribeLocalEvent<InputMoverComponent, UpdateIsPredictedEvent>(OnUpdatePredicted);
|
||||
SubscribeLocalEvent<MovementRelayTargetComponent, UpdateIsPredictedEvent>(OnUpdateRelayTargetPredicted);
|
||||
SubscribeLocalEvent<PullableComponent, UpdateIsPredictedEvent>(OnUpdatePullablePredicted);
|
||||
}
|
||||
|
||||
private void OnUpdatePredicted(Entity<InputMoverComponent> entity, ref UpdateIsPredictedEvent args)
|
||||
{
|
||||
// Enable prediction if an entity is controlled by the player
|
||||
if (entity.Owner == _playerManager.LocalEntity)
|
||||
args.IsPredicted = true;
|
||||
}
|
||||
|
||||
private void OnUpdateRelayTargetPredicted(Entity<MovementRelayTargetComponent> entity, ref UpdateIsPredictedEvent args)
|
||||
{
|
||||
if (entity.Comp.Source == _playerManager.LocalEntity)
|
||||
args.IsPredicted = true;
|
||||
}
|
||||
|
||||
private void OnUpdatePullablePredicted(Entity<PullableComponent> entity, ref UpdateIsPredictedEvent args)
|
||||
{
|
||||
// Enable prediction if an entity is being pulled by the player.
|
||||
// Disable prediction if an entity is being pulled by some non-player entity.
|
||||
|
||||
if (entity.Comp.Puller == _playerManager.LocalEntity)
|
||||
args.IsPredicted = true;
|
||||
else if (entity.Comp.Puller != null)
|
||||
args.BlockPrediction = true;
|
||||
|
||||
// TODO recursive pulling checks?
|
||||
// What if the entity is being pulled by a vehicle controlled by the player?
|
||||
}
|
||||
|
||||
private void OnRelayPlayerAttached(Entity<RelayInputMoverComponent> entity, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
Physics.UpdateIsPredicted(entity.Owner);
|
||||
Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
|
||||
if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
|
||||
SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
|
||||
}
|
||||
|
||||
private void OnRelayPlayerDetached(Entity<RelayInputMoverComponent> entity, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
Physics.UpdateIsPredicted(entity.Owner);
|
||||
Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
|
||||
if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
|
||||
SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(Entity<InputMoverComponent> entity, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
SetMoveInput(entity, MoveButtons.None);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<InputMoverComponent> entity, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
SetMoveInput(entity, MoveButtons.None);
|
||||
}
|
||||
|
||||
public override void UpdateBeforeSolve(bool prediction, float frameTime)
|
||||
{
|
||||
base.UpdateBeforeSolve(prediction, frameTime);
|
||||
|
||||
if (_playerManager.LocalEntity is not {Valid: true} player)
|
||||
return;
|
||||
|
||||
if (RelayQuery.TryGetComponent(player, out var relayMover))
|
||||
HandleClientsideMovement(relayMover.RelayEntity, frameTime);
|
||||
|
||||
HandleClientsideMovement(player, frameTime);
|
||||
}
|
||||
|
||||
private void HandleClientsideMovement(EntityUid player, float frameTime)
|
||||
{
|
||||
if (!MoverQuery.TryGetComponent(player, out var mover) ||
|
||||
!XformQuery.TryGetComponent(player, out var xform))
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RelayInputMoverComponent, LocalPlayerAttachedEvent>(OnRelayPlayerAttached);
|
||||
SubscribeLocalEvent<RelayInputMoverComponent, LocalPlayerDetachedEvent>(OnRelayPlayerDetached);
|
||||
SubscribeLocalEvent<InputMoverComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<InputMoverComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
SubscribeLocalEvent<InputMoverComponent, UpdateIsPredictedEvent>(OnUpdatePredicted);
|
||||
SubscribeLocalEvent<MovementRelayTargetComponent, UpdateIsPredictedEvent>(OnUpdateRelayTargetPredicted);
|
||||
SubscribeLocalEvent<PullableComponent, UpdateIsPredictedEvent>(OnUpdatePullablePredicted);
|
||||
return;
|
||||
}
|
||||
|
||||
private void OnUpdatePredicted(Entity<InputMoverComponent> entity, ref UpdateIsPredictedEvent args)
|
||||
var physicsUid = player;
|
||||
PhysicsComponent? body;
|
||||
var xformMover = xform;
|
||||
|
||||
if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid))
|
||||
{
|
||||
// Enable prediction if an entity is controlled by the player
|
||||
if (entity.Owner == _playerManager.LocalEntity)
|
||||
args.IsPredicted = true;
|
||||
}
|
||||
|
||||
private void OnUpdateRelayTargetPredicted(Entity<MovementRelayTargetComponent> entity, ref UpdateIsPredictedEvent args)
|
||||
{
|
||||
if (entity.Comp.Source == _playerManager.LocalEntity)
|
||||
args.IsPredicted = true;
|
||||
}
|
||||
|
||||
private void OnUpdatePullablePredicted(Entity<PullableComponent> entity, ref UpdateIsPredictedEvent args)
|
||||
{
|
||||
// Enable prediction if an entity is being pulled by the player.
|
||||
// Disable prediction if an entity is being pulled by some non-player entity.
|
||||
|
||||
if (entity.Comp.Puller == _playerManager.LocalEntity)
|
||||
args.IsPredicted = true;
|
||||
else if (entity.Comp.Puller != null)
|
||||
args.BlockPrediction = true;
|
||||
|
||||
// TODO recursive pulling checks?
|
||||
// What if the entity is being pulled by a vehicle controlled by the player?
|
||||
}
|
||||
|
||||
private void OnRelayPlayerAttached(Entity<RelayInputMoverComponent> entity, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
Physics.UpdateIsPredicted(entity.Owner);
|
||||
Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
|
||||
if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
|
||||
SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
|
||||
}
|
||||
|
||||
private void OnRelayPlayerDetached(Entity<RelayInputMoverComponent> entity, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
Physics.UpdateIsPredicted(entity.Owner);
|
||||
Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
|
||||
if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
|
||||
SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(Entity<InputMoverComponent> entity, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
SetMoveInput(entity, MoveButtons.None);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<InputMoverComponent> entity, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
SetMoveInput(entity, MoveButtons.None);
|
||||
}
|
||||
|
||||
public override void UpdateBeforeSolve(bool prediction, float frameTime)
|
||||
{
|
||||
base.UpdateBeforeSolve(prediction, frameTime);
|
||||
|
||||
if (_playerManager.LocalEntity is not {Valid: true} player)
|
||||
return;
|
||||
|
||||
if (RelayQuery.TryGetComponent(player, out var relayMover))
|
||||
HandleClientsideMovement(relayMover.RelayEntity, frameTime);
|
||||
|
||||
HandleClientsideMovement(player, frameTime);
|
||||
}
|
||||
|
||||
private void HandleClientsideMovement(EntityUid player, float frameTime)
|
||||
{
|
||||
if (!MoverQuery.TryGetComponent(player, out var mover) ||
|
||||
!XformQuery.TryGetComponent(player, out var xform))
|
||||
if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) ||
|
||||
!XformQuery.TryGetComponent(xform.ParentUid, out xformMover))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var physicsUid = player;
|
||||
PhysicsComponent? body;
|
||||
var xformMover = xform;
|
||||
|
||||
if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid))
|
||||
{
|
||||
if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) ||
|
||||
!XformQuery.TryGetComponent(xform.ParentUid, out xformMover))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
physicsUid = xform.ParentUid;
|
||||
}
|
||||
else if (!PhysicsQuery.TryGetComponent(player, out body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Server-side should just be handled on its own so we'll just do this shizznit
|
||||
HandleMobMovement(
|
||||
player,
|
||||
mover,
|
||||
physicsUid,
|
||||
body,
|
||||
xformMover,
|
||||
frameTime);
|
||||
physicsUid = xform.ParentUid;
|
||||
}
|
||||
|
||||
protected override bool CanSound()
|
||||
else if (!PhysicsQuery.TryGetComponent(player, out body))
|
||||
{
|
||||
return _timing is { IsFirstTimePredicted: true, InSimulation: true };
|
||||
return;
|
||||
}
|
||||
|
||||
// Server-side should just be handled on its own so we'll just do this shizznit
|
||||
HandleMobMovement(
|
||||
player,
|
||||
mover,
|
||||
physicsUid,
|
||||
body,
|
||||
xformMover,
|
||||
frameTime);
|
||||
}
|
||||
|
||||
protected override bool CanSound()
|
||||
{
|
||||
return _timing is { IsFirstTimePredicted: true, InSimulation: true };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -152,7 +152,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ namespace Content.Client.Power.APC
|
|||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<ApcMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.OnBreaker += BreakerPressed;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ public sealed class ActivatableUIRequiresPowerSystem : SharedActivatableUIRequir
|
|||
return;
|
||||
}
|
||||
|
||||
if (TryComp<WiresPanelComponent>(ent.Owner, out var panel) && panel.Open)
|
||||
return;
|
||||
|
||||
_popup.PopupClient(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent.Owner)), args.User, args.User);
|
||||
args.Cancel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System.IO.Compression;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Launcher;
|
||||
using Content.Client.MainMenu;
|
||||
using Content.Client.Replay.Spectator;
|
||||
using Content.Client.Replay.UI.Loading;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Systems.Chat;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Effects;
|
||||
|
|
@ -26,8 +24,6 @@ using Robust.Client.Replays.Playback;
|
|||
using Robust.Client.State;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
|
|
@ -60,7 +56,7 @@ public sealed class ContentReplayPlaybackManager
|
|||
public bool IsScreenshotMode = false;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Most recently loaded file, for re-attempting the load with error tolerance.
|
||||
/// Required because the zip reader auto-disposes and I'm too lazy to change it so that
|
||||
|
|
@ -96,32 +92,17 @@ public sealed class ContentReplayPlaybackManager
|
|||
return;
|
||||
}
|
||||
|
||||
ReturnToDefaultState();
|
||||
if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
|
||||
_client.StopSinglePlayer();
|
||||
|
||||
// Show a popup window with the error message
|
||||
var text = Loc.GetString("replay-loading-failed", ("reason", exception));
|
||||
var box = new BoxContainer
|
||||
Action? retryAction = null;
|
||||
Action? cancelAction = null;
|
||||
|
||||
if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is { } last)
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children = {new Label {Text = text}}
|
||||
};
|
||||
|
||||
var popup = new DefaultWindow { Title = "Error!" };
|
||||
popup.Contents.AddChild(box);
|
||||
|
||||
// Add button for attempting to re-load the replay while ignoring some errors.
|
||||
if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is {} last)
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
Text = Loc.GetString("replay-loading-retry"),
|
||||
StyleClasses = { StyleBase.ButtonCaution }
|
||||
};
|
||||
|
||||
button.OnPressed += _ =>
|
||||
retryAction = () =>
|
||||
{
|
||||
_cfg.SetCVar(CVars.ReplayIgnoreErrors, true);
|
||||
popup.Dispose();
|
||||
|
||||
IReplayFileReader reader = last.Zip == null
|
||||
? new ReplayFileReaderResources(_resMan, last.Folder)
|
||||
|
|
@ -129,11 +110,20 @@ public sealed class ContentReplayPlaybackManager
|
|||
|
||||
_loadMan.LoadAndStartReplay(reader);
|
||||
};
|
||||
|
||||
box.AddChild(button);
|
||||
}
|
||||
|
||||
popup.OpenCentered();
|
||||
// If we have an explicit menu to get back to (e.g. replay browser UI), show a cancel button.
|
||||
if (DefaultState != null)
|
||||
{
|
||||
cancelAction = () =>
|
||||
{
|
||||
_stateMan.RequestStateChange(DefaultState);
|
||||
};
|
||||
}
|
||||
|
||||
// Switch to a new game state to present the error and cancel/retry options.
|
||||
var state = _stateMan.RequestStateChange<ReplayLoadingFailed>();
|
||||
state.SetData(exception, cancelAction, retryAction);
|
||||
}
|
||||
|
||||
public void ReturnToDefaultState()
|
||||
|
|
|
|||
36
Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs
Normal file
36
Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Replay.UI.Loading;
|
||||
|
||||
/// <summary>
|
||||
/// State used to display an error message if a replay failed to load.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReplayLoadingFailedControl"/>
|
||||
/// <seealso cref="ContentReplayPlaybackManager"/>
|
||||
public sealed class ReplayLoadingFailed : State
|
||||
{
|
||||
[Dependency] private readonly IStylesheetManager _stylesheetManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
|
||||
|
||||
private ReplayLoadingFailedControl? _control;
|
||||
|
||||
public void SetData(Exception exception, Action? cancelPressed, Action? retryPressed)
|
||||
{
|
||||
DebugTools.Assert(_control != null);
|
||||
_control.SetData(exception, cancelPressed, retryPressed);
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
_control = new ReplayLoadingFailedControl(_stylesheetManager);
|
||||
_userInterface.StateRoot.AddChild(_control);
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
_control?.Orphan();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:pllax="clr-namespace:Content.Client.Parallax">
|
||||
<pllax:ParallaxControl />
|
||||
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Orientation="Vertical" SetSize="800 600" Margin="2">
|
||||
<ScrollContainer Name="what" VerticalExpand="True" HScrollEnabled="False" Margin="0 0 0 2" ReturnMeasure="True">
|
||||
<RichTextLabel Name="ReasonLabel" VerticalAlignment="Top" />
|
||||
</ScrollContainer>
|
||||
<Button Name="RetryButton" StyleClasses="Caution" Text="{Loc 'replay-loading-retry'}" Visible="False" />
|
||||
<Button Name="CancelButton" Text="{Loc 'replay-loading-cancel'}" Visible="False" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
</Control>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Replay.UI.Loading;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ReplayLoadingFailedControl : Control
|
||||
{
|
||||
public ReplayLoadingFailedControl(IStylesheetManager stylesheet)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Stylesheet = stylesheet.SheetSpace;
|
||||
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
|
||||
}
|
||||
|
||||
public void SetData(Exception exception, Action? cancelPressed, Action? retryPressed)
|
||||
{
|
||||
ReasonLabel.SetMessage(
|
||||
FormattedMessage.FromUnformatted(Loc.GetString("replay-loading-failed", ("reason", exception))));
|
||||
|
||||
if (cancelPressed != null)
|
||||
{
|
||||
CancelButton.Visible = true;
|
||||
CancelButton.OnPressed += _ =>
|
||||
{
|
||||
cancelPressed();
|
||||
};
|
||||
}
|
||||
|
||||
if (retryPressed != null)
|
||||
{
|
||||
RetryButton.Visible = true;
|
||||
RetryButton.OnPressed += _ =>
|
||||
{
|
||||
retryPressed();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -68,9 +68,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};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
SeparationOverride="4">
|
||||
<EntityPrototypeView
|
||||
Name="ItemPrototype"
|
||||
Margin="4 4"
|
||||
Margin="4 0 0 0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MinSize="32 32"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Name="MainContainer" Orientation="Vertical">
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'vending-machine-component-search-filter'}" HorizontalExpand="True" Margin ="4 4"/>
|
||||
<co:SearchListContainer Name="VendingContents" VerticalExpand="True" Margin="4 0"/>
|
||||
<co:SearchListContainer Name="VendingContents" VerticalExpand="True" Margin="4 4"/>
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Popups;
|
||||
|
|
@ -7,6 +8,7 @@ using Content.Shared.Examine;
|
|||
using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.ComponentTrees;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
|
|
@ -21,10 +23,10 @@ namespace Content.Client.Verbs
|
|||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly ExamineSystem _examine = default!;
|
||||
[Dependency] private readonly SpriteTreeSystem _tree = 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 IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -32,8 +34,6 @@ namespace Content.Client.Verbs
|
|||
/// </summary>
|
||||
public const float EntityMenuLookupSize = 0.25f;
|
||||
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// These flags determine what entities the user can see on the context menu.
|
||||
/// </summary>
|
||||
|
|
@ -41,6 +41,8 @@ namespace Content.Client.Verbs
|
|||
|
||||
public Action<VerbsResponseEvent>? OnVerbsResponse;
|
||||
|
||||
private List<EntityUid> _entities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
|
@ -77,49 +79,50 @@ namespace Content.Client.Verbs
|
|||
visibility = ev.Visibility;
|
||||
|
||||
// Get entities
|
||||
List<EntityUid> entities;
|
||||
var examineFlags = LookupFlags.All & ~LookupFlags.Sensors;
|
||||
_entities.Clear();
|
||||
var entitiesUnderMouse = _tree.QueryAabb(targetPos.MapId, Box2.CenteredAround(targetPos.Position, new Vector2(EntityMenuLookupSize, EntityMenuLookupSize)));
|
||||
|
||||
// Do we have to do FoV checks?
|
||||
if ((visibility & MenuVisibility.NoFov) == 0)
|
||||
{
|
||||
var entitiesUnderMouse = gameScreenBase.GetClickableEntities(targetPos).ToHashSet();
|
||||
bool Predicate(EntityUid e) => e == player || entitiesUnderMouse.Contains(e);
|
||||
bool Predicate(EntityUid e) => e == player;
|
||||
|
||||
TryComp(player.Value, out ExaminerComponent? examiner);
|
||||
|
||||
entities = new();
|
||||
foreach (var ent in _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags))
|
||||
foreach (var ent in entitiesUnderMouse)
|
||||
{
|
||||
if (_examine.CanExamine(player.Value, targetPos, Predicate, ent, examiner))
|
||||
entities.Add(ent);
|
||||
if (_examine.CanExamine(player.Value, targetPos, Predicate, ent.Uid, examiner))
|
||||
_entities.Add(ent.Uid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags).ToList();
|
||||
foreach (var ent in entitiesUnderMouse)
|
||||
{
|
||||
_entities.Add(ent.Uid);
|
||||
}
|
||||
}
|
||||
|
||||
if (entities.Count == 0)
|
||||
if (_entities.Count == 0)
|
||||
return false;
|
||||
|
||||
if (visibility == MenuVisibility.All)
|
||||
{
|
||||
result = entities;
|
||||
result = new (_entities);
|
||||
return true;
|
||||
}
|
||||
|
||||
// remove any entities in containers
|
||||
if ((visibility & MenuVisibility.InContainer) == 0)
|
||||
{
|
||||
for (var i = entities.Count - 1; i >= 0; i--)
|
||||
for (var i = _entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var entity = entities[i];
|
||||
var entity = _entities[i];
|
||||
|
||||
if (ContainerSystem.IsInSameOrTransparentContainer(player.Value, entity))
|
||||
continue;
|
||||
|
||||
entities.RemoveSwap(i);
|
||||
_entities.RemoveSwap(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,23 +131,23 @@ namespace Content.Client.Verbs
|
|||
{
|
||||
var spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
|
||||
for (var i = entities.Count - 1; i >= 0; i--)
|
||||
for (var i = _entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var entity = entities[i];
|
||||
var entity = _entities[i];
|
||||
|
||||
if (!spriteQuery.TryGetComponent(entity, out var spriteComponent) ||
|
||||
!spriteComponent.Visible ||
|
||||
_tagSystem.HasTag(entity, "HideContextMenu"))
|
||||
{
|
||||
entities.RemoveSwap(i);
|
||||
_entities.RemoveSwap(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entities.Count == 0)
|
||||
if (_entities.Count == 0)
|
||||
return false;
|
||||
|
||||
result = entities;
|
||||
result = new(_entities);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -98,4 +98,24 @@ public sealed class ResearchTest
|
|||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AllLatheRecipesValidTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
|
||||
var server = pair.Server;
|
||||
var proto = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var recipe in proto.EnumeratePrototypes<LatheRecipePrototype>())
|
||||
{
|
||||
if (recipe.Result == null)
|
||||
Assert.That(recipe.ResultReagents, Is.Not.Null, $"Recipe '{recipe.ID}' has no result or result reagents.");
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
||||
|
|
@ -133,13 +132,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;
|
||||
|
|
@ -99,6 +101,7 @@ public sealed class AdminSystem : EntitySystem
|
|||
SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
|
||||
SubscribeLocalEvent<ActorComponent, EntityRenamedEvent>(OnPlayerRenamed);
|
||||
|
||||
IoCManager.Instance!.TryResolveType(out _sponsorsManager); // Sunrise-Sponsors
|
||||
}
|
||||
|
|
@ -127,6 +130,11 @@ public sealed class AdminSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
private void OnPlayerRenamed(Entity<ActorComponent> ent, ref EntityRenamedEvent args)
|
||||
{
|
||||
UpdatePlayerList(ent.Comp.PlayerSession);
|
||||
}
|
||||
|
||||
public void UpdatePlayerList(ICommonSession player)
|
||||
{
|
||||
_playerList[player.UserId] = GetPlayerInfo(player.Data, player);
|
||||
|
|
@ -392,30 +400,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) &&
|
||||
|
|
@ -439,30 +449,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()
|
||||
{
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue