diff --git a/.github/Schemas/mapfile_requirements.txt b/.github/Schemas/mapfile_requirements.txt
new file mode 100644
index 00000000000..aa4d4d0668b
--- /dev/null
+++ b/.github/Schemas/mapfile_requirements.txt
@@ -0,0 +1 @@
+pyyaml==6.0.2
diff --git a/.github/Schemas/rga.yml b/.github/Schemas/rga.yml
new file mode 100644
index 00000000000..a83e6c43346
--- /dev/null
+++ b/.github/Schemas/rga.yml
@@ -0,0 +1,20 @@
+# If this gets updated, make sure to also update https://github.com/space-wizards/RobustToolboxSpecifications
+
+list(include('attribution'), min=1)
+---
+attribution:
+ files: list(str())
+ license: license()
+ copyright: str()
+ source: url()
+
+# Example
+# - files: ["deprecated.png"]
+# license: "MIT"
+# copyright: "created by 20kdc"
+# source: "https://github.com/ParadiseSS13/Paradise"
+#
+# - files: ["arcadeblue2.png", "boxing.png", "carpetclown.png", "carpetoffice.png", "gym.png", "metaldiamond.png"]
+# license: "CC-BY-NC-SA-3.0"
+# copyright: "by WALPVRGIS for Goonstation, taken at commit 236551b95a5b24917c72f3069223026b2dc4e690 from floors.dmi"
+# source: "https://github.com/goonstation/goonstation"
diff --git a/.github/Schemas/rga_requirements.txt b/.github/Schemas/rga_requirements.txt
new file mode 100644
index 00000000000..af6467bc963
--- /dev/null
+++ b/.github/Schemas/rga_requirements.txt
@@ -0,0 +1,2 @@
+validators
+pyyaml==6.0.2
diff --git a/.github/Schemas/rga_validators.py b/.github/Schemas/rga_validators.py
new file mode 100644
index 00000000000..7c13194dd79
--- /dev/null
+++ b/.github/Schemas/rga_validators.py
@@ -0,0 +1,29 @@
+from yamale.validators import Validator
+import validators
+
+class License(Validator):
+ tag = "license"
+ licenses = [
+ "CC-BY-3.0",
+ "CC-BY-4.0",
+ "CC-BY-SA-3.0",
+ "CC-BY-SA-4.0",
+ "CC-BY-NC-3.0",
+ "CC-BY-NC-4.0",
+ "CC-BY-NC-SA-3.0",
+ "CC-BY-NC-SA-4.0",
+ "CC-BY-ND-4.0",
+ "CC0-1.0",
+ "MIT",
+ "Custom" # implies that the license is described in the copyright field.
+ ]
+
+ def _is_valid(self, value):
+ return value in self.licenses
+
+class Url(Validator):
+ tag = "url"
+
+ def _is_valid(self, value):
+ # Source field is required to ensure its not neglected, but there may be no applicable URL
+ return (value == "NA") or validators.url(value)
diff --git a/.github/workflows/validate-rgas.yml b/.github/workflows/validate-rgas.yml
index 72ce39ceff6..7d8be69f765 100644
--- a/.github/workflows/validate-rgas.yml
+++ b/.github/workflows/validate-rgas.yml
@@ -19,7 +19,7 @@ jobs:
uses: space-wizards/submodule-dependency@v0.1.5
- uses: PaulRitter/yaml-schema-validator@v1
with:
- schema: RobustToolbox/Schemas/rga.yml
+ schema: .github/Schemas/rga.yml
path_pattern: .*attributions.ya?ml$
validators_path: RobustToolbox/Schemas/rga_validators.py
- validators_requirements: RobustToolbox/Schemas/rga_requirements.txt
+ validators_requirements: .github/Schemas/rga_requirements.txt
diff --git a/.github/workflows/validate_mapfiles.yml b/.github/workflows/validate_mapfiles.yml
index 190ee97d8fe..1a4fcaa4b2d 100644
--- a/.github/workflows/validate_mapfiles.yml
+++ b/.github/workflows/validate_mapfiles.yml
@@ -22,4 +22,4 @@ jobs:
schema: RobustToolbox/Schemas/mapfile.yml
path_pattern: .*Resources/Maps/.*
validators_path: RobustToolbox/Schemas/mapfile_validators.py
- validators_requirements: RobustToolbox/Schemas/mapfile_requirements.txt
+ validators_requirements: .github/Schemas/mapfile_requirements.txt
diff --git a/Content.Client/Advertise/Systems/SpeakOnUIClosedSystem.cs b/Content.Client/Advertise/Systems/SpeakOnUIClosedSystem.cs
new file mode 100644
index 00000000000..4e82ec4d006
--- /dev/null
+++ b/Content.Client/Advertise/Systems/SpeakOnUIClosedSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Advertise.Systems;
+
+namespace Content.Client.Advertise.Systems;
+
+public sealed class SpeakOnUIClosedSystem : SharedSpeakOnUIClosedSystem;
diff --git a/Content.Client/Atmos/Components/PipeColorVisualsComponent.cs b/Content.Client/Atmos/Components/PipeColorVisualsComponent.cs
index 355b10cb4a4..9b24b1adc25 100644
--- a/Content.Client/Atmos/Components/PipeColorVisualsComponent.cs
+++ b/Content.Client/Atmos/Components/PipeColorVisualsComponent.cs
@@ -1,8 +1,4 @@
-using Robust.Shared.GameObjects;
-
namespace Content.Client.Atmos.Components;
[RegisterComponent]
-public sealed partial class PipeColorVisualsComponent : Component
-{
-}
+public sealed partial class PipeColorVisualsComponent : Component;
diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml
index 96f136abf0e..a5eddfdf064 100644
--- a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml
+++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml
@@ -1,6 +1,5 @@
@@ -62,7 +61,7 @@
-
+
diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
index b95fdf12b16..a086f76f272 100644
--- a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
+++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
@@ -136,8 +136,9 @@ public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlert
GasGridContainer.RemoveAllChildren();
var gasData = focusData.Value.GasData.Where(g => g.Key != Gas.Oxygen);
+ var keyValuePairs = gasData.ToList();
- if (gasData.Count() == 0)
+ if (keyValuePairs.Count == 0)
{
// No other gases
var gasLabel = new Label()
@@ -158,7 +159,7 @@ public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlert
else
{
// Add an entry for each gas
- foreach ((var gas, (var mol, var percent, var alert)) in gasData)
+ foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs)
{
FixedPoint2 gasPercent = percent * 100f;
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs
index a31eb898fef..8abf0cbd73a 100644
--- a/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs
+++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs
@@ -16,8 +16,6 @@ protected override void Open()
_menu = new AtmosAlertsComputerWindow(this, Owner);
_menu.OpenCentered();
_menu.OnClose += Close;
-
- EntMan.TryGetComponent(Owner, out var xform);
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -26,9 +24,6 @@ protected override void UpdateState(BoundUserInterfaceState state)
var castState = (AtmosAlertsComputerBoundInterfaceState) state;
- if (castState == null)
- return;
-
EntMan.TryGetComponent(Owner, out var xform);
_menu?.UpdateUI(xform?.Coordinates, castState.AirAlarms, castState.FireAlarms, castState.FocusData);
}
diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
index 8824a776ee6..e5ede1b92e3 100644
--- a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
+++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
@@ -1,7 +1,6 @@
(OnInit);
- SubscribeLocalEvent(OnAppearanceChanged, after: new[] { typeof(SubFloorHideSystem) });
+ SubscribeLocalEvent(OnAppearanceChanged, after: [typeof(SubFloorHideSystem)]);
}
private void OnInit(EntityUid uid, PipeAppearanceComponent component, ComponentInit args)
@@ -84,7 +82,8 @@ private void OnAppearanceChanged(EntityUid uid, PipeAppearanceComponent componen
layer.Visible &= visible;
- if (!visible) continue;
+ if (!visible)
+ continue;
layer.Color = color;
}
diff --git a/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs b/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs
index 019f25f376b..18ca2234752 100644
--- a/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs
+++ b/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs
@@ -1,12 +1,7 @@
-using System.Collections.Generic;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Power;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Atmos.Monitor;
@@ -27,7 +22,7 @@ protected override void OnAppearanceChange(EntityUid uid, AtmosAlarmableVisualsC
{
foreach (var visLayer in component.HideOnDepowered)
{
- if (args.Sprite.LayerMapTryGet(visLayer, out int powerVisibilityLayer))
+ if (args.Sprite.LayerMapTryGet(visLayer, out var powerVisibilityLayer))
args.Sprite.LayerSetVisible(powerVisibilityLayer, powered);
}
}
@@ -36,7 +31,7 @@ protected override void OnAppearanceChange(EntityUid uid, AtmosAlarmableVisualsC
{
foreach (var (setLayer, powerState) in component.SetOnDepowered)
{
- if (args.Sprite.LayerMapTryGet(setLayer, out int setStateLayer))
+ if (args.Sprite.LayerMapTryGet(setLayer, out var setStateLayer))
args.Sprite.LayerSetState(setStateLayer, new RSI.StateId(powerState));
}
}
diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
index 2ae15188355..650f96eec97 100644
--- a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
+++ b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
@@ -1,11 +1,7 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Log;
namespace Content.Client.Atmos.Monitor.UI;
@@ -30,7 +26,6 @@ protected override void Open()
_window.AirAlarmModeChanged += OnAirAlarmModeChanged;
_window.AutoModeChanged += OnAutoModeChanged;
_window.ResyncAllRequested += ResyncAllDevices;
- _window.AirAlarmTabChange += OnTabChanged;
}
private void ResyncAllDevices()
@@ -63,11 +58,6 @@ private void OnThresholdChanged(string address, AtmosMonitorThresholdType type,
SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas));
}
- private void OnTabChanged(AirAlarmTab tab)
- {
- SendMessage(new AirAlarmTabSetMessage(tab));
- }
-
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
@@ -84,6 +74,7 @@ protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
- if (disposing) _window?.Dispose();
+ if (disposing)
+ _window?.Dispose();
}
}
diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
index eeec11c7660..f0201dc81ba 100644
--- a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
@@ -8,7 +8,6 @@
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -23,7 +22,6 @@ public sealed partial class AirAlarmWindow : FancyWindow
public event Action? AirAlarmModeChanged;
public event Action? AutoModeChanged;
public event Action? ResyncAllRequested;
- public event Action? AirAlarmTabChange;
private RichTextLabel _address => CDeviceAddress;
private RichTextLabel _deviceTotal => CDeviceTotal;
@@ -60,7 +58,7 @@ public AirAlarmWindow()
AirAlarmMode.Fill => "air-alarm-ui-mode-fill",
AirAlarmMode.Panic => "air-alarm-ui-mode-panic",
AirAlarmMode.None => "air-alarm-ui-mode-none",
- _ => "error"
+ _ => "error",
};
_modes.AddItem(Loc.GetString(text));
}
@@ -71,7 +69,7 @@ public AirAlarmWindow()
AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id);
};
- _autoMode.OnToggled += args =>
+ _autoMode.OnToggled += _ =>
{
AutoModeChanged!.Invoke(_autoMode.Pressed);
};
@@ -80,11 +78,6 @@ public AirAlarmWindow()
_tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers"));
_tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors"));
- _tabContainer.OnTabChanged += idx =>
- {
- AirAlarmTabChange!((AirAlarmTab) idx);
- };
-
_resyncDevices.OnPressed += _ =>
{
_ventDevices.RemoveAllChildren();
@@ -117,8 +110,6 @@ public void UpdateState(AirAlarmUIState state)
{
UpdateDeviceData(addr, dev);
}
-
- _tabContainer.CurrentTab = (int) state.Tab;
}
public void UpdateModeSelector(AirAlarmMode mode)
@@ -139,8 +130,8 @@ public void UpdateDeviceData(string addr, IAtmosDeviceData device)
if (!_pumps.TryGetValue(addr, out var pumpControl))
{
var control= new PumpControl(pump, addr);
- control.PumpDataChanged += AtmosDeviceDataChanged!.Invoke;
- control.PumpDataCopied += AtmosDeviceDataCopied!.Invoke;
+ control.PumpDataChanged += AtmosDeviceDataChanged;
+ control.PumpDataCopied += AtmosDeviceDataCopied;
_pumps.Add(addr, control);
CVentContainer.AddChild(control);
}
@@ -154,8 +145,8 @@ public void UpdateDeviceData(string addr, IAtmosDeviceData device)
if (!_scrubbers.TryGetValue(addr, out var scrubberControl))
{
var control = new ScrubberControl(scrubber, addr);
- control.ScrubberDataChanged += AtmosDeviceDataChanged!.Invoke;
- control.ScrubberDataCopied += AtmosDeviceDataCopied!.Invoke;
+ control.ScrubberDataChanged += AtmosDeviceDataChanged;
+ control.ScrubberDataCopied += AtmosDeviceDataCopied;
_scrubbers.Add(addr, control);
CScrubberContainer.AddChild(control);
}
@@ -170,6 +161,7 @@ public void UpdateDeviceData(string addr, IAtmosDeviceData device)
{
var control = new SensorInfo(sensor, addr);
control.OnThresholdUpdate += AtmosAlarmThresholdChanged;
+ control.SensorDataCopied += AtmosDeviceDataCopied;
_sensors.Add(addr, control);
CSensorContainer.AddChild(control);
}
@@ -184,22 +176,18 @@ public void UpdateDeviceData(string addr, IAtmosDeviceData device)
public static Color ColorForThreshold(float amount, AtmosAlarmThreshold threshold)
{
- threshold.CheckThreshold(amount, out AtmosAlarmType curAlarm);
+ threshold.CheckThreshold(amount, out var curAlarm);
return ColorForAlarm(curAlarm);
}
public static Color ColorForAlarm(AtmosAlarmType curAlarm)
{
- if(curAlarm == AtmosAlarmType.Danger)
+ return curAlarm switch
{
- return StyleNano.DangerousRedFore;
- }
- else if(curAlarm == AtmosAlarmType.Warning)
- {
- return StyleNano.ConcerningOrangeFore;
- }
-
- return StyleNano.GoodGreenFore;
+ AtmosAlarmType.Danger => StyleNano.DangerousRedFore,
+ AtmosAlarmType.Warning => StyleNano.ConcerningOrangeFore,
+ _ => StyleNano.GoodGreenFore,
+ };
}
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs
index 17b03b84684..8502cd2712b 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs
@@ -1,12 +1,8 @@
-using System;
-using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Localization;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
@@ -25,7 +21,7 @@ public sealed partial class PumpControl : BoxContainer
private OptionButton _pressureCheck => CPressureCheck;
private FloatSpinBox _externalBound => CExternalBound;
private FloatSpinBox _internalBound => CInternalBound;
- private Button _copySettings => CCopySettings;
+ private Button _copySettings => CCopySettings;
public PumpControl(GasVentPumpData data, string address)
{
@@ -86,11 +82,11 @@ public PumpControl(GasVentPumpData data, string address)
_data.PressureChecks = (VentPressureBound) args.Id;
PumpDataChanged?.Invoke(_address, _data);
};
-
- _copySettings.OnPressed += _ =>
- {
- PumpDataCopied?.Invoke(_data);
- };
+
+ _copySettings.OnPressed += _ =>
+ {
+ PumpDataCopied?.Invoke(_data);
+ };
}
public void ChangeData(GasVentPumpData data)
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
index f2241bcd8da..d03ac77ab31 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
@@ -1,15 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
using Content.Shared.Atmos;
-using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Localization;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
@@ -27,7 +21,7 @@ public sealed partial class ScrubberControl : BoxContainer
private OptionButton _pumpDirection => CPumpDirection;
private FloatSpinBox _volumeRate => CVolumeRate;
private CheckBox _wideNet => CWideNet;
- private Button _copySettings => CCopySettings;
+ private Button _copySettings => CCopySettings;
private GridContainer _gases => CGasContainer;
private Dictionary _gasControls = new();
@@ -77,11 +71,11 @@ public ScrubberControl(GasVentScrubberData data, string address)
_data.PumpDirection = (ScrubberPumpDirection) args.Id;
ScrubberDataChanged?.Invoke(_address, _data);
};
-
- _copySettings.OnPressed += _ =>
- {
- ScrubberDataCopied?.Invoke(_data);
- };
+
+ _copySettings.OnPressed += _ =>
+ {
+ ScrubberDataCopied?.Invoke(_data);
+ };
foreach (var value in Enum.GetValues())
{
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml b/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml
index 005e6807b37..c50dadc9c7d 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml
@@ -3,6 +3,9 @@
+
+
+
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
index da602cd7479..f906bd39300 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
@@ -12,12 +12,14 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets;
public sealed partial class SensorInfo : BoxContainer
{
public Action? OnThresholdUpdate;
+ public event Action? SensorDataCopied;
private string _address;
private ThresholdControl _pressureThreshold;
private ThresholdControl _temperatureThreshold;
private Dictionary _gasThresholds = new();
private Dictionary _gasLabels = new();
+ private Button _copySettings => CCopySettings;
public SensorInfo(AtmosSensorData data, string address)
{
@@ -43,7 +45,8 @@ public SensorInfo(AtmosSensorData data, string address)
var label = new RichTextLabel();
var fractionGas = amount / data.TotalMoles;
- label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
+ label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
+ ("gas", $"{gas}"),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
@@ -53,9 +56,9 @@ public SensorInfo(AtmosSensorData data, string address)
var threshold = data.GasThresholds[gas];
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
- gasThresholdControl.ThresholdDataChanged += (type, threshold, arg3) =>
+ gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
{
- OnThresholdUpdate!(_address, type, threshold, arg3);
+ OnThresholdUpdate?.Invoke(_address, type, alarmThreshold, arg3);
};
_gasThresholds.Add(gas, gasThresholdControl);
@@ -64,18 +67,24 @@ public SensorInfo(AtmosSensorData data, string address)
_pressureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), data.PressureThreshold, AtmosMonitorThresholdType.Pressure);
PressureThresholdContainer.AddChild(_pressureThreshold);
- _temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"), data.TemperatureThreshold,
+ _temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"),
+ data.TemperatureThreshold,
AtmosMonitorThresholdType.Temperature);
TemperatureThresholdContainer.AddChild(_temperatureThreshold);
_pressureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
{
- OnThresholdUpdate!(_address, type, threshold, arg3);
+ OnThresholdUpdate?.Invoke(_address, type, threshold, arg3);
};
_temperatureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
{
- OnThresholdUpdate!(_address, type, threshold, arg3);
+ OnThresholdUpdate?.Invoke(_address, type, threshold, arg3);
+ };
+
+ _copySettings.OnPressed += _ =>
+ {
+ SensorDataCopied?.Invoke(data);
};
}
@@ -103,7 +112,8 @@ public void ChangeData(AtmosSensorData data)
}
var fractionGas = amount / data.TotalMoles;
- label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
+ label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
+ ("gas", $"{gas}"),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml.cs
index 3612d84de4c..55f7c008987 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml.cs
@@ -1,7 +1,4 @@
-using Content.Client.Message;
-using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
-using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs
index 78c73fa573a..651620f3e25 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs
@@ -1,12 +1,8 @@
-using System;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
-using Content.Shared.Atmos.Monitor.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Localization;
// holy FUCK
// this technically works because some of this you can *not* do in XAML but holy FUCK
@@ -115,29 +111,38 @@ public ThresholdControl(string name, AtmosAlarmThreshold threshold, AtmosMonitor
_enabled.Pressed = !_threshold.Ignore;
}
- private String LabelForBound(string boundType) //, DebugMessage)> state) =>
{
if (_system.TileData.TryGetValue(uid, out var data))
diff --git a/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs b/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs
index ad496caa8ec..f838a69fdf1 100644
--- a/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs
@@ -1,4 +1,5 @@
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
namespace Content.Client.Atmos.UI
@@ -16,9 +17,7 @@ protected override void Open()
{
base.Open();
- _window = new GasAnalyzerWindow();
- _window.OnClose += OnClose;
- _window.OpenCenteredLeft();
+ _window = this.CreateWindowCenteredLeft();
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
diff --git a/Content.Client/Atmos/UI/SpaceHeaterWindow.xaml b/Content.Client/Atmos/UI/SpaceHeaterWindow.xaml
index 1b7bd490b85..003a2203d56 100644
--- a/Content.Client/Atmos/UI/SpaceHeaterWindow.xaml
+++ b/Content.Client/Atmos/UI/SpaceHeaterWindow.xaml
@@ -1,6 +1,6 @@
+ MinSize="280 160" Title="{Loc comp-space-heater-ui-title}">
diff --git a/Content.Client/Audio/ClientGlobalSoundSystem.cs b/Content.Client/Audio/ClientGlobalSoundSystem.cs
index 50c3971d95a..882ab1be6d3 100644
--- a/Content.Client/Audio/ClientGlobalSoundSystem.cs
+++ b/Content.Client/Audio/ClientGlobalSoundSystem.cs
@@ -66,7 +66,7 @@ private void PlayAdminSound(AdminSoundEvent soundEvent)
{
if(!_adminAudioEnabled) return;
- var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
+ var stream = _audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
_adminAudio.Add(stream?.Entity);
}
@@ -75,13 +75,13 @@ private void PlayStationEventMusic(StationEventMusicEvent soundEvent)
// Either the cvar is disabled or it's already playing
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
- var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
+ var stream = _audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
_eventAudio.Add(soundEvent.Type, stream?.Entity);
}
private void PlayGameSound(GameGlobalSoundEvent soundEvent)
{
- _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
+ _audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
}
private void StopStationEventMusic(StopStationEventMusic soundEvent)
diff --git a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
index 7d7d77f51a3..fda2c0062c7 100644
--- a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
+++ b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
@@ -218,7 +218,7 @@ private void PlayRestartSound(RoundRestartCleanupEvent ev)
return;
var file = _gameTicker.RestartSound;
- if (string.IsNullOrEmpty(file))
+ if (ResolvedSoundSpecifier.IsNullOrEmpty(file))
{
return;
}
diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs
index c26976ffbe4..6fe6e308de2 100644
--- a/Content.Client/Buckle/BuckleSystem.cs
+++ b/Content.Client/Buckle/BuckleSystem.cs
@@ -3,13 +3,15 @@
using Content.Shared.Buckle.Components;
using Content.Shared.Rotation;
using Robust.Client.GameObjects;
-using Robust.Shared.GameStates;
+using Robust.Client.Graphics;
namespace Content.Client.Buckle;
internal sealed class BuckleSystem : SharedBuckleSystem
{
[Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
+ [Dependency] private readonly IEyeManager _eye = default!;
+ [Dependency] private readonly SharedTransformSystem _xformSystem = default!;
public override void Initialize()
{
@@ -21,37 +23,6 @@ public override void Initialize()
SubscribeLocalEvent(OnUnbuckledEvent);
}
- ///
- /// Is the strap entity already rotated north? Lower the draw depth of the buckled entity.
- ///
- private void OnBuckledEvent(Entity ent, ref BuckledEvent args)
- {
- if (!TryComp(args.Strap, out var strapSprite) ||
- !TryComp(ent.Owner, out var buckledSprite))
- return;
-
- if (Transform(args.Strap.Owner).LocalRotation.GetCardinalDir() == Direction.North)
- {
- ent.Comp.OriginalDrawDepth ??= buckledSprite.DrawDepth;
- buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
- }
- }
-
- ///
- /// Was the draw depth of the buckled entity lowered? Reset it upon unbuckling.
- ///
- private void OnUnbuckledEvent(Entity ent, ref UnbuckledEvent args)
- {
- if (!TryComp(ent.Owner, out var buckledSprite))
- return;
-
- if (ent.Comp.OriginalDrawDepth.HasValue)
- {
- buckledSprite.DrawDepth = ent.Comp.OriginalDrawDepth.Value;
- ent.Comp.OriginalDrawDepth = null;
- }
- }
-
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
{
// I'm moving this to the client-side system, but for the sake of posterity let's keep this comment:
@@ -61,13 +32,21 @@ private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveE
// This code is garbage, it doesn't work with rotated viewports. I need to finally get around to reworking
// sprite rendering for entity layers & direction dependent sorting.
+ // Future notes:
+ // Right now this doesn't handle: other grids, other grids rotating, the camera rotation changing, and many other fun rotation specific things
+ // The entire thing should be a concern of the engine, or something engine helps to implement properly.
+ // Give some of the sprite rotations their own drawdepth, maybe as an offset within the rsi, or something like this
+ // And we won't ever need to set the draw depth manually
+
if (args.NewRotation == args.OldRotation)
return;
if (!TryComp(uid, out var strapSprite))
return;
- var isNorth = Transform(uid).LocalRotation.GetCardinalDir() == Direction.North;
+ var angle = _xformSystem.GetWorldRotation(uid) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough
+
+ var isNorth = angle.GetCardinalDir() == Direction.North;
foreach (var buckledEntity in component.BuckledEntities)
{
if (!TryComp(buckledEntity, out var buckle))
@@ -78,6 +57,7 @@ private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveE
if (isNorth)
{
+ // This will only assign if empty, it won't get overwritten by new depth on multiple calls, which do happen easily
buckle.OriginalDrawDepth ??= buckledSprite.DrawDepth;
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
}
@@ -89,6 +69,42 @@ private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveE
}
}
+ ///
+ /// Lower the draw depth of the buckled entity without needing for the strap entity to rotate/move.
+ /// Only do so when the entity is facing screen-local north
+ ///
+ private void OnBuckledEvent(Entity ent, ref BuckledEvent args)
+ {
+ if (!TryComp(args.Strap, out var strapSprite))
+ return;
+
+ if (!TryComp(ent.Owner, out var buckledSprite))
+ return;
+
+ var angle = _xformSystem.GetWorldRotation(args.Strap) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough
+
+ if (angle.GetCardinalDir() != Direction.North)
+ return;
+
+ ent.Comp.OriginalDrawDepth ??= buckledSprite.DrawDepth;
+ buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
+ }
+
+ ///
+ /// Was the draw depth of the buckled entity lowered? Reset it upon unbuckling.
+ ///
+ private void OnUnbuckledEvent(Entity ent, ref UnbuckledEvent args)
+ {
+ if (!TryComp(ent.Owner, out var buckledSprite))
+ return;
+
+ if (!ent.Comp.OriginalDrawDepth.HasValue)
+ return;
+
+ buckledSprite.DrawDepth = ent.Comp.OriginalDrawDepth.Value;
+ ent.Comp.OriginalDrawDepth = null;
+ }
+
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
{
if (!TryComp(uid, out var rotVisuals)
diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
index 44c40143d83..04075000f5b 100644
--- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -39,6 +39,6 @@ protected override void UpdateState(BoundUserInterfaceState message)
if (message is not CargoBountyConsoleState state)
return;
- _menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
+ _menu?.UpdateEntries(state.Bounties, state.History, state.UntilNextSkip);
}
}
diff --git a/Content.Client/Cargo/Systems/ClientPriceGunSystem.cs b/Content.Client/Cargo/Systems/ClientPriceGunSystem.cs
new file mode 100644
index 00000000000..35fb2112c07
--- /dev/null
+++ b/Content.Client/Cargo/Systems/ClientPriceGunSystem.cs
@@ -0,0 +1,22 @@
+using Content.Shared.Cargo.Components;
+using Content.Shared.Timing;
+using Content.Shared.Cargo.Systems;
+
+namespace Content.Client.Cargo.Systems;
+
+///
+/// This handles...
+///
+public sealed class ClientPriceGunSystem : SharedPriceGunSystem
+{
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
+
+ protected override bool GetPriceOrBounty(Entity entity, EntityUid target, EntityUid user)
+ {
+ if (!TryComp(entity, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((entity, useDelay)))
+ return false;
+
+ // It feels worse if the cooldown is predicted but the popup isn't! So only do the cooldown reset on the server.
+ return true;
+ }
+}
diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml
new file mode 100644
index 00000000000..905cf020ed1
--- /dev/null
+++ b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
new file mode 100644
index 00000000000..54804be641c
--- /dev/null
+++ b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
@@ -0,0 +1,49 @@
+using Content.Client.Message;
+using Content.Shared.Cargo;
+using Content.Shared.Cargo.Prototypes;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Cargo.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class BountyHistoryEntry : BoxContainer
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public BountyHistoryEntry(CargoBountyHistoryData bounty)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
+ return;
+
+ var items = new List();
+ foreach (var entry in bountyPrototype.Entries)
+ {
+ items.Add(Loc.GetString("bounty-console-manifest-entry",
+ ("amount", entry.Amount),
+ ("item", Loc.GetString(entry.Name))));
+ }
+
+ ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
+ RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
+ IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
+
+ TimestampLabel.SetMarkup(bounty.Timestamp.ToString(@"hh\:mm\:ss"));
+
+ if (bounty.Result == CargoBountyHistoryData.BountyResult.Completed)
+ {
+ NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-completed-label"));
+ }
+ else
+ {
+ NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-skipped-label",
+ ("id", bounty.ActorName ?? "")));
+ }
+ }
+}
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml b/Content.Client/Cargo/UI/CargoBountyMenu.xaml
index bb263ff6c4a..526ba69129b 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml
@@ -11,15 +11,28 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
index 3767b45e4be..c289fb6ed83 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
@@ -15,9 +15,12 @@ public sealed partial class CargoBountyMenu : FancyWindow
public CargoBountyMenu()
{
RobustXamlLoader.Load(this);
+
+ MasterTabContainer.SetTabTitle(0, Loc.GetString("bounty-console-tab-available-label"));
+ MasterTabContainer.SetTabTitle(1, Loc.GetString("bounty-console-tab-history-label"));
}
- public void UpdateEntries(List bounties, TimeSpan untilNextSkip)
+ public void UpdateEntries(List bounties, List history, TimeSpan untilNextSkip)
{
BountyEntriesContainer.Children.Clear();
foreach (var b in bounties)
@@ -32,5 +35,21 @@ public void UpdateEntries(List bounties, TimeSpan untilNextSkip
{
MinHeight = 10
});
+
+ BountyHistoryContainer.Children.Clear();
+ if (history.Count == 0)
+ {
+ NoHistoryLabel.Visible = true;
+ }
+ else
+ {
+ NoHistoryLabel.Visible = false;
+
+ // Show the history in reverse, so last entry is first in the list
+ for (var i = history.Count - 1; i >= 0; i--)
+ {
+ BountyHistoryContainer.AddChild(new BountyHistoryEntry(history[i]));
+ }
+ }
}
}
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
index aaf3900beee..235e7b0fef9 100644
--- a/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
@@ -1,4 +1,5 @@
using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader;
using Content.Shared.CartridgeLoader.Cartridges;
using Robust.Client.UserInterface;
@@ -13,16 +14,23 @@ public override Control GetUIFragmentRoot()
return _fragment!;
}
- public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
+ public override void Setup(BoundUserInterface ui, EntityUid? fragmentOwner)
{
_fragment = new LogProbeUiFragment();
+
+ _fragment.OnPrintPressed += () =>
+ {
+ var ev = new LogProbePrintMessage();
+ var message = new CartridgeUiMessage(ev);
+ ui.SendMessage(message);
+ };
}
public override void UpdateState(BoundUserInterfaceState state)
{
- if (state is not LogProbeUiState logProbeUiState)
+ if (state is not LogProbeUiState cast)
return;
- _fragment?.UpdateState(logProbeUiState); // DeltaV - just take the state
+ _fragment?.UpdateState(cast); // DeltaV - just take the state
}
}
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
index a0769590e91..6c2fdb25262 100644
--- a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
@@ -38,4 +38,9 @@
+
+
+
+
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
index 5fa93bb40db..e6e879ae1c0 100644
--- a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
@@ -1,7 +1,7 @@
-using System.Linq; // DeltaV
-using Content.Client.DeltaV.CartridgeLoader.Cartridges; // DeltaV
+using System.Linq;
+using Content.Client.DeltaV.CartridgeLoader.Cartridges;
using Content.Shared.CartridgeLoader.Cartridges;
-using Content.Shared.DeltaV.CartridgeLoader.Cartridges; // DeltaV
+using Content.Shared.DeltaV.CartridgeLoader.Cartridges;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -11,14 +11,24 @@ namespace Content.Client.CartridgeLoader.Cartridges;
[GenerateTypedNameReferences]
public sealed partial class LogProbeUiFragment : BoxContainer
{
+ ///
+ /// Action invoked when the print button gets pressed.
+ ///
+ public Action? OnPrintPressed;
+
public LogProbeUiFragment()
{
RobustXamlLoader.Load(this);
+
+ PrintButton.OnPressed += _ => OnPrintPressed?.Invoke();
}
// DeltaV begin - Update to handle both types of data
public void UpdateState(LogProbeUiState state)
{
+ EntityName.Text = state.EntityName;
+ PrintButton.Disabled = string.IsNullOrEmpty(state.EntityName);
+
ProbedDeviceContainer.RemoveAllChildren();
if (state.NanoChatData != null)
diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
index b74df979cf4..7fa07a6a6e4 100644
--- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
+++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
@@ -53,7 +53,7 @@
diff --git a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
index 44501767dd4..55d7c8835d5 100644
--- a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
+++ b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
@@ -21,11 +21,10 @@ public CrayonBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey
protected override void Open()
{
base.Open();
- _menu = this.CreateWindow();
+ _menu = this.CreateWindowCenteredLeft();
_menu.OnColorSelected += SelectColor;
_menu.OnSelected += Select;
PopulateCrayons();
- _menu.OpenCenteredLeft();
}
private void PopulateCrayons()
diff --git a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
index 2fe56fcce99..da548c1e540 100644
--- a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
+++ b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
@@ -144,7 +144,7 @@ private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, Sprite
{
KeyFrames =
{
- new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(unit.FlushSound), 0)
+ new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(unit.FlushSound), 0)
}
});
}
diff --git a/Content.Client/Forensics/ScentTrackerSystem.cs b/Content.Client/Forensics/ScentTrackerSystem.cs
index 4e6254502aa..94f40b03b78 100644
--- a/Content.Client/Forensics/ScentTrackerSystem.cs
+++ b/Content.Client/Forensics/ScentTrackerSystem.cs
@@ -1,31 +1,31 @@
+using Content.Shared.Forensics.Systems;
using Content.Shared.Forensics;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Client.Player;
-namespace Content.Client.Forensics
+namespace Content.Client.Forensics;
+
+public sealed class ScentTrackerSystem : SharedScentTrackerSystem
{
- public sealed class ScentTrackerSystem : EntitySystem
- {
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
- var query = AllEntityQuery();
- while (query.MoveNext(out var uid, out var comp))
- if (TryComp(_playerManager.LocalEntity, out var scentcomp)
- && scentcomp.Scent != string.Empty
- && scentcomp.Scent == comp.Scent
- && _timing.CurTime > comp.TargetTime)
- {
- comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(1.0f);
- Spawn("ScentTrackEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
- }
- }
+ var query = AllEntityQuery();
+ while (query.MoveNext(out var uid, out var comp))
+ if (TryComp(_playerManager.LocalEntity, out var scentcomp)
+ && scentcomp.Scent != string.Empty
+ && scentcomp.Scent == comp.Scent
+ && _timing.CurTime > comp.TargetTime)
+ {
+ comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(1.0f);
+ Spawn("ScentTrackEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
+ }
}
}
\ No newline at end of file
diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
index a5bc6340544..79229c12b4d 100644
--- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs
+++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
@@ -11,6 +11,7 @@
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
+using Robust.Shared.Audio;
namespace Content.Client.GameTicking.Managers
{
@@ -27,7 +28,7 @@ public sealed class ClientGameTicker : SharedGameTicker
[ViewVariables] public bool AreWeReady { get; private set; }
[ViewVariables] public bool IsGameStarted { get; private set; }
- [ViewVariables] public string? RestartSound { get; private set; }
+ [ViewVariables] public ResolvedSoundSpecifier? RestartSound { get; private set; }
[ViewVariables] public LobbyBackgroundPrototype? LobbyBackground { get; private set; }
[ViewVariables] public bool DisallowedLateJoin { get; private set; }
[ViewVariables] public string? ServerInfoBlob { get; private set; }
diff --git a/Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml b/Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml
new file mode 100644
index 00000000000..fb09b5cbf48
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml.cs
new file mode 100644
index 00000000000..1ae09fc8fe5
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml.cs
@@ -0,0 +1,183 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Client.Guidebook.Richtext;
+using Content.Client.Message;
+using Content.Client.UserInterface.ControlExtensions;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Kitchen;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Guidebook.Controls;
+
+///
+/// Control for embedding a microwave recipe into a guidebook.
+///
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag, ISearchableControl
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly ILogManager _logManager = default!;
+
+ private ISawmill _sawmill = default!;
+
+ public GuideMicrowaveEmbed()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ MouseFilter = MouseFilterMode.Stop;
+
+ _sawmill = _logManager.GetSawmill("guidemicrowaveembed");
+ }
+
+ public GuideMicrowaveEmbed(string recipe) : this()
+ {
+ GenerateControl(_prototype.Index(recipe));
+ }
+
+ public GuideMicrowaveEmbed(FoodRecipePrototype recipe) : this()
+ {
+ GenerateControl(recipe);
+ }
+
+ public bool CheckMatchesSearch(string query)
+ {
+ return this.ChildrenContainText(query);
+ }
+
+ public void SetHiddenState(bool state, string query)
+ {
+ Visible = CheckMatchesSearch(query) ? state : !state;
+ }
+
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ control = null;
+ if (!args.TryGetValue("Recipe", out var id))
+ {
+ _sawmill.Error("Recipe embed tag is missing recipe prototype argument");
+ return false;
+ }
+
+ if (!_prototype.TryIndex(id, out var recipe))
+ {
+ _sawmill.Error($"Specified recipe prototype \"{id}\" is not a valid recipe prototype");
+ return false;
+ }
+
+ GenerateControl(recipe);
+
+ control = this;
+ return true;
+ }
+
+ private void GenerateHeader(FoodRecipePrototype recipe)
+ {
+ var entity = _prototype.Index(recipe.Result);
+
+ IconContainer.AddChild(new GuideEntityEmbed(recipe.Result, false, false));
+ ResultName.SetMarkup(entity.Name);
+ ResultDescription.SetMarkup(entity.Description);
+ }
+
+ private void GenerateSolidIngredients(FoodRecipePrototype recipe)
+ {
+ foreach (var (product, amount) in recipe.IngredientsSolids.OrderByDescending(p => p.Value))
+ {
+ var ingredient = _prototype.Index(product);
+
+ IngredientsGrid.AddChild(new GuideEntityEmbed(product, false, false));
+
+ // solid name
+
+ var solidNameMsg = new FormattedMessage();
+ solidNameMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-solid-name-display", ("ingredient", ingredient.Name)));
+ solidNameMsg.Pop();
+
+ var solidNameLabel = new RichTextLabel();
+ solidNameLabel.SetMessage(solidNameMsg);
+
+ IngredientsGrid.AddChild(solidNameLabel);
+
+ // solid quantity
+
+ var solidQuantityMsg = new FormattedMessage();
+ solidQuantityMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-solid-quantity-display", ("amount", amount)));
+ solidQuantityMsg.Pop();
+
+ var solidQuantityLabel = new RichTextLabel();
+ solidQuantityLabel.SetMessage(solidQuantityMsg);
+
+ IngredientsGrid.AddChild(solidQuantityLabel);
+ }
+ }
+
+ private void GenerateLiquidIngredients(FoodRecipePrototype recipe)
+ {
+ foreach (var (product, amount) in recipe.IngredientsReagents.OrderByDescending(p => p.Value))
+ {
+ var reagent = _prototype.Index(product);
+
+ // liquid color
+
+ var liquidColorMsg = new FormattedMessage();
+ liquidColorMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-color-display", ("color", reagent.SubstanceColor)));
+ liquidColorMsg.Pop();
+
+ var liquidColorLabel = new RichTextLabel();
+ liquidColorLabel.SetMessage(liquidColorMsg);
+ liquidColorLabel.HorizontalAlignment = Control.HAlignment.Center;
+
+ IngredientsGrid.AddChild(liquidColorLabel);
+
+ // liquid name
+
+ var liquidNameMsg = new FormattedMessage();
+ liquidNameMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-name-display", ("reagent", reagent.LocalizedName)));
+ liquidNameMsg.Pop();
+
+ var liquidNameLabel = new RichTextLabel();
+ liquidNameLabel.SetMessage(liquidNameMsg);
+
+ IngredientsGrid.AddChild(liquidNameLabel);
+
+ // liquid quantity
+
+ var liquidQuantityMsg = new FormattedMessage();
+ liquidQuantityMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-quantity-display", ("amount", amount)));
+ liquidQuantityMsg.Pop();
+
+ var liquidQuantityLabel = new RichTextLabel();
+ liquidQuantityLabel.SetMessage(liquidQuantityMsg);
+
+ IngredientsGrid.AddChild(liquidQuantityLabel);
+ }
+ }
+
+ private void GenerateIngredients(FoodRecipePrototype recipe)
+ {
+ GenerateLiquidIngredients(recipe);
+ GenerateSolidIngredients(recipe);
+ }
+
+ private void GenerateCookTime(FoodRecipePrototype recipe)
+ {
+ var msg = new FormattedMessage();
+ msg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-cook-time", ("time", recipe.CookTime)));
+ msg.Pop();
+
+ CookTimeLabel.SetMessage(msg);
+ }
+
+ private void GenerateControl(FoodRecipePrototype recipe)
+ {
+ GenerateHeader(recipe);
+ GenerateIngredients(recipe);
+ GenerateCookTime(recipe);
+ }
+}
diff --git a/Content.Client/Guidebook/Controls/GuideMicrowaveGroupEmbed.cs b/Content.Client/Guidebook/Controls/GuideMicrowaveGroupEmbed.cs
new file mode 100644
index 00000000000..098e99459c9
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideMicrowaveGroupEmbed.cs
@@ -0,0 +1,59 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Client.Guidebook.Richtext;
+using Content.Shared.Kitchen;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Guidebook.Controls;
+
+///
+/// Control for listing microwave recipes in a guidebook
+///
+[UsedImplicitly]
+public sealed partial class GuideMicrowaveGroupEmbed : BoxContainer, IDocumentTag
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public GuideMicrowaveGroupEmbed()
+ {
+ Orientation = LayoutOrientation.Vertical;
+ IoCManager.InjectDependencies(this);
+ MouseFilter = MouseFilterMode.Stop;
+ }
+
+ public GuideMicrowaveGroupEmbed(string group) : this()
+ {
+ CreateEntries(group);
+ }
+
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ control = null;
+ if (!args.TryGetValue("Group", out var group))
+ {
+ Logger.Error("Microwave group embed tag is missing group argument");
+ return false;
+ }
+
+ CreateEntries(group);
+
+ control = this;
+ return true;
+ }
+
+ private void CreateEntries(string group)
+ {
+ var prototypes = _prototype.EnumeratePrototypes()
+ .Where(p => p.Group.Equals(group))
+ .OrderBy(p => p.Name);
+
+ foreach (var recipe in prototypes)
+ {
+ var embed = new GuideMicrowaveEmbed(recipe);
+ AddChild(embed);
+ }
+ }
+}
diff --git a/Content.Client/Guidebook/Controls/GuidebookError.xaml b/Content.Client/Guidebook/Controls/GuidebookError.xaml
new file mode 100644
index 00000000000..b84d527ea0a
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuidebookError.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuidebookError.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookError.xaml.cs
new file mode 100644
index 00000000000..461f196c838
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuidebookError.xaml.cs
@@ -0,0 +1,23 @@
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Guidebook.Controls;
+
+[UsedImplicitly] [GenerateTypedNameReferences]
+public sealed partial class GuidebookError : BoxContainer
+{
+ public GuidebookError()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public GuidebookError(string original, string? error) : this()
+ {
+ Original.AddText(original);
+
+ if (error is not null)
+ Error.AddText(error);
+ }
+}
diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
index 018f27077cd..69e1f4b4a4e 100644
--- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
@@ -4,12 +4,10 @@
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree;
using Content.Client.UserInterface.Systems.Info;
-using Content.Shared.CCVar;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
@@ -18,15 +16,18 @@ namespace Content.Client.Guidebook.Controls;
[GenerateTypedNameReferences]
public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
{
- [Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
+ [Dependency] private readonly IResourceManager _resourceManager = default!;
private Dictionary, GuideEntry> _entries = new();
+ private readonly ISawmill _sawmill;
+
public GuidebookWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
+ _sawmill = Logger.GetSawmill("Guidebook");
Tree.OnSelectedItemChanged += OnSelectionChanged;
@@ -36,6 +37,20 @@ public GuidebookWindow()
};
}
+ public void HandleClick(string link)
+ {
+ if (!_entries.TryGetValue(link, out var entry))
+ return;
+
+ if (Tree.TryGetIndexFromMetadata(entry, out var index))
+ {
+ Tree.ExpandParentEntries(index.Value);
+ Tree.SetSelectedIndex(index);
+ }
+ else
+ ShowGuide(entry);
+ }
+
private void OnSelectionChanged(TreeItem? item)
{
if (item != null && item.Metadata is GuideEntry entry)
@@ -71,8 +86,9 @@ private void ShowGuide(GuideEntry entry)
if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd()))
{
- EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
- Logger.GetSawmill("guidebook.window").Error($"Failed to parse contents of guide document {entry.Id}.");
+ // The guidebook will automatically display the in-guidebook error if it fails
+
+ _sawmill.Error($"Failed to parse contents of guide document {entry.Id}.");
}
}
@@ -113,43 +129,39 @@ private IEnumerable GetSortedEntries(List> entries = new(_entries.Keys);
foreach (var entry in _entries.Values)
{
- if (entry.Children.Count > 0)
- {
- var sortedChildren = entry.Children
- .Select(childId => _entries[childId])
- .OrderBy(childEntry => childEntry.Priority)
- .ThenBy(childEntry => Loc.GetString(childEntry.Name))
- .Select(childEntry => new ProtoId(childEntry.Id))
- .ToList();
-
- entry.Children = sortedChildren;
- }
entries.ExceptWith(entry.Children);
}
rootEntries = entries.ToList();
}
+ // Only roots need to be sorted.
+ // As defined in the SS14 Dev Wiki, children are already sorted based on their child field order within their parent's prototype definition.
+ // Roots are sorted by priority. If there is no defined priority for a root then it is by definition sorted undefined.
return rootEntries
.Select(rootEntryId => _entries[rootEntryId])
.OrderBy(rootEntry => rootEntry.Priority)
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
}
- private void RepopulateTree(List>? roots = null, ProtoId? forcedRoot = null)
+ private void RepopulateTree(List>? roots = null,
+ ProtoId? forcedRoot = null)
{
Tree.Clear();
HashSet> addedEntries = new();
- TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
+ var parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
foreach (var entry in GetSortedEntries(roots))
{
AddEntry(entry.Id, parent, addedEntries);
}
+
Tree.SetAllExpanded(true);
}
- private TreeItem? AddEntry(ProtoId id, TreeItem? parent, HashSet> addedEntries)
+ private TreeItem? AddEntry(ProtoId id,
+ TreeItem? parent,
+ HashSet> addedEntries)
{
if (!_entries.TryGetValue(id, out var entry))
return null;
@@ -179,22 +191,6 @@ private void RepopulateTree(List>? roots = null, Pr
return item;
}
- public void HandleClick(string link)
- {
- if (!_entries.TryGetValue(link, out var entry))
- return;
-
- if (Tree.TryGetIndexFromMetadata(entry, out var index))
- {
- Tree.ExpandParentEntries(index.Value);
- Tree.SetSelectedIndex(index);
- }
- else
- {
- ShowGuide(entry);
- }
- }
-
private void HandleFilter()
{
var emptySearch = SearchBar.Text.Trim().Length == 0;
@@ -208,6 +204,5 @@ private void HandleFilter()
element.SetHiddenState(true, SearchBar.Text.Trim());
}
}
-
}
}
diff --git a/Content.Client/Guidebook/DocumentParsingManager.cs b/Content.Client/Guidebook/DocumentParsingManager.cs
index 7df7a49f95c..857ae552024 100644
--- a/Content.Client/Guidebook/DocumentParsingManager.cs
+++ b/Content.Client/Guidebook/DocumentParsingManager.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using Content.Client.Guidebook.Controls;
using Content.Client.Guidebook.Richtext;
using Content.Shared.Guidebook;
using Pidgin;
@@ -7,6 +8,7 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Sandboxing;
+using Robust.Shared.Utility;
using static Pidgin.Parser;
namespace Content.Client.Guidebook;
@@ -22,8 +24,10 @@ public sealed partial class DocumentParsingManager
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;
private readonly Dictionary> _tagControlParsers = new();
- private Parser _tagParser = default!;
private Parser _controlParser = default!;
+
+ private ISawmill _sawmill = default!;
+ private Parser _tagParser = default!;
public Parser> ControlParser = default!;
public void Initialize()
@@ -32,7 +36,8 @@ public void Initialize()
.Assert(_tagControlParsers.ContainsKey, tag => $"unknown tag: {tag}")
.Bind(tag => _tagControlParsers[tag]);
- _controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser).Before(SkipWhitespaces);
+ _controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser)
+ .Before(SkipWhitespaces);
foreach (var typ in _reflectionManager.GetAllChildren())
{
@@ -40,6 +45,8 @@ public void Initialize()
}
ControlParser = SkipWhitespaces.Then(_controlParser.Many());
+
+ _sawmill = Logger.GetSawmill("Guidebook");
}
public bool TryAddMarkup(Control control, ProtoId entryId, bool log = true)
@@ -68,37 +75,57 @@ public bool TryAddMarkup(Control control, string text, bool log = true)
}
catch (Exception e)
{
- if (log)
- Logger.GetSawmill("document.parsing.addmarkup").Error($"Encountered error while generating markup controls: {e}");
+ _sawmill.Error($"Encountered error while generating markup controls: {e}");
+
+ control.AddChild(new GuidebookError(text, e.ToStringBetter()));
+
return false;
}
return true;
}
- private Parser CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox) => Map(
- (args, controls) =>
- {
- var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
- if (!tag.TryParseTag(args, out var control))
- {
- Logger.GetSawmill("document.parsing").Error($"Failed to parse {tagId} args");
- return new Control();
- }
+ private Parser CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox)
+ {
+ return Map(
+ (args, controls) =>
+ {
+ try
+ {
+ var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
+ if (!tag.TryParseTag(args, out var control))
+ {
+ _sawmill.Error($"Failed to parse {tagId} args");
+ return new GuidebookError(args.ToString() ?? tagId, $"Failed to parse {tagId} args");
+ }
- foreach (var child in controls)
- {
- control.AddChild(child);
- }
- return control;
- },
- ParseTagArgs(tagId),
- TagContentParser(tagId)).Labelled($"{tagId} control");
+ foreach (var child in controls)
+ {
+ control.AddChild(child);
+ }
+
+ return control;
+ }
+ catch (Exception e)
+ {
+ var output = args.Aggregate(string.Empty,
+ (current, pair) => current + $"{pair.Key}=\"{pair.Value}\" ");
+
+ _sawmill.Error($"Tag: {tagId} \n Arguments: {output}/>");
+ return new GuidebookError($"Tag: {tagId}\nArguments: {output}", e.ToString());
+ }
+ },
+ ParseTagArgs(tagId),
+ TagContentParser(tagId))
+ .Labelled($"{tagId} control");
+ }
// Parse a bunch of controls until we encounter a matching closing tag.
- private Parser> TagContentParser(string tag) =>
- OneOf(
- Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty()),
- TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
- );
+ private Parser> TagContentParser(string tag)
+ {
+ return OneOf(
+ Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty()),
+ TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
+ );
+ }
}
diff --git a/Content.Client/Guidebook/DocumentParsingManager.static.cs b/Content.Client/Guidebook/DocumentParsingManager.static.cs
index ab38fcb1546..5d25d8f6452 100644
--- a/Content.Client/Guidebook/DocumentParsingManager.static.cs
+++ b/Content.Client/Guidebook/DocumentParsingManager.static.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using Content.Client.Guidebook.Controls;
using Pidgin;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -14,92 +15,142 @@ public sealed partial class DocumentParsingManager
{
private const string ListBullet = " › ";
- #region Text Parsing
- #region Basic Text Parsing
- // Try look for an escaped character. If found, skip the escaping slash and return the character.
- private static readonly Parser TryEscapedChar = Try(Char('\\').Then(OneOf(
- Try(Char('<')),
- Try(Char('>')),
- Try(Char('\\')),
- Try(Char('-')),
- Try(Char('=')),
- Try(Char('"')),
- Try(Char(' ')),
- Try(Char('n')).ThenReturn('\n'),
- Try(Char('t')).ThenReturn('\t')
- )));
+ // Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point).
+ private static readonly Parser TryEscapedChar = Try(Char('\\')
+ .Then(OneOf(
+ Try(Char('<')),
+ Try(Char('>')),
+ Try(Char('\\')),
+ Try(Char('-')),
+ Try(Char('=')),
+ Try(Char('"')),
+ Try(Char(' ')),
+ Try(Char('n')).ThenReturn('\n'),
+ Try(Char('t')).ThenReturn('\t')
+ )));
private static readonly Parser SkipNewline = Whitespace.SkipUntil(Char('\n'));
- private static readonly Parser TrySingleNewlineToSpace = Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' ');
+ private static readonly Parser TrySingleNewlineToSpace =
+ Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' ');
private static readonly Parser TextChar = OneOf(
TryEscapedChar, // consume any backslashed being used to escape text
TrySingleNewlineToSpace, // turn single newlines into spaces
Any // just return the character.
- );
+ );
- // like TextChar, but not skipping whitespace around newlines
private static readonly Parser QuotedTextChar = OneOf(TryEscapedChar, Any);
+ private static readonly Parser QuotedText =
+ Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text");
+
+ private static readonly Parser TryStartList =
+ Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
+
+ private static readonly Parser TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
+
+ private static readonly Parser TryStartParagraph =
+ Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
+
+ private static readonly Parser TryLookTextEnd =
+ Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
+
+ private static readonly Parser TextParser =
+ TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
+
+ private static readonly Parser TextControlParser = Try(Map(text =>
+ {
+ var rt = new RichTextLabel
+ {
+ HorizontalExpand = true,
+ Margin = new Thickness(0, 0, 0, 15.0f)
+ };
+
+ var msg = new FormattedMessage();
+ // THANK YOU RICHTEXT VERY COOL
+ // (text doesn't default to white).
+ msg.PushColor(Color.White);
+
+ // If the parsing fails, don't throw an error and instead make an inline error message
+ string? error;
+ if (!msg.TryAddMarkup(text, out error))
+ {
+ Logger.GetSawmill("Guidebook").Error("Failed to parse RichText in Guidebook");
+
+ return new GuidebookError(text, error);
+ }
+
+ msg.Pop();
+ rt.SetMessage(msg);
+ return rt;
+ },
+ TextParser)
+ .Cast())
+ .Labelled("richtext");
+
+ private static readonly Parser HeaderControlParser = Try(Char('#'))
+ .Then(SkipWhitespaces.Then(Map(text => new Label
+ {
+ Text = text,
+ StyleClasses = { "LabelHeadingBigger" }
+ },
+ AnyCharExcept('\n').AtLeastOnceString())
+ .Cast()))
+ .Labelled("header");
+
+ private static readonly Parser SubHeaderControlParser = Try(String("##"))
+ .Then(SkipWhitespaces.Then(Map(text => new Label
+ {
+ Text = text,
+ StyleClasses = { "LabelHeading" }
+ },
+ AnyCharExcept('\n').AtLeastOnceString())
+ .Cast()))
+ .Labelled("subheader");
+
+ private static readonly Parser TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
+
+ private static readonly Parser ListControlParser = Try(Char('-'))
+ .Then(SkipWhitespaces)
+ .Then(Map(
+ control => new BoxContainer
+ {
+ Children = { new Label { Text = ListBullet, VerticalAlignment = VAlignment.Top }, control },
+ Orientation = LayoutOrientation.Horizontal
+ },
+ TextControlParser)
+ .Cast())
+ .Labelled("list");
+
+ #region Text Parsing
+
+ #region Basic Text Parsing
+
+ // Try look for an escaped character. If found, skip the escaping slash and return the character.
+
+
+ // like TextChar, but not skipping whitespace around newlines
+
+
// Quoted text
- private static readonly Parser QuotedText = Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text");
+
#endregion
#region rich text-end markers
- private static readonly Parser TryStartList = Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
- private static readonly Parser TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
- private static readonly Parser TryStartParagraph = Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
- private static readonly Parser TryLookTextEnd = Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
+
#endregion
// parses text characters until it hits a text-end
- private static readonly Parser TextParser = TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
- private static readonly Parser TextControlParser = Try(Map(text =>
- {
- var rt = new RichTextLabel()
- {
- HorizontalExpand = true,
- Margin = new Thickness(0, 0, 0, 15.0f),
- };
-
- var msg = new FormattedMessage();
- // THANK YOU RICHTEXT VERY COOL
- // (text doesn't default to white).
- msg.PushColor(Color.White);
- msg.AddMarkup(text);
- msg.Pop();
- rt.SetMessage(msg);
- return rt;
- }, TextParser).Cast()).Labelled("richtext");
#endregion
#region Headers
- private static readonly Parser HeaderControlParser = Try(Char('#')).Then(SkipWhitespaces.Then(Map(text => new Label()
- {
- Text = text,
- StyleClasses = { "LabelHeadingBigger" }
- }, AnyCharExcept('\n').AtLeastOnceString()).Cast())).Labelled("header");
- private static readonly Parser SubHeaderControlParser = Try(String("##")).Then(SkipWhitespaces.Then(Map(text => new Label()
- {
- Text = text,
- StyleClasses = { "LabelHeading" }
- }, AnyCharExcept('\n').AtLeastOnceString()).Cast())).Labelled("subheader");
-
- private static readonly Parser TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
#endregion
- // Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point).
- private static readonly Parser ListControlParser = Try(Char('-')).Then(SkipWhitespaces).Then(Map(
- control => new BoxContainer()
- {
- Children = { new Label() { Text = ListBullet, VerticalAlignment = VAlignment.Top, }, control },
- Orientation = LayoutOrientation.Horizontal,
- }, TextControlParser).Cast()).Labelled("list");
-
#region Tag Parsing
+
// closing brackets for tags
private static readonly Parser TagEnd = Char('>').Then(SkipWhitespaces);
private static readonly Parser ImmediateTagEnd = String("/>").Then(SkipWhitespaces);
@@ -107,20 +158,24 @@ public sealed partial class DocumentParsingManager
private static readonly Parser TryLookTagEnd = Lookahead(OneOf(Try(TagEnd), Try(ImmediateTagEnd)));
//parse tag argument key. any normal text character up until we hit a "="
- private static readonly Parser TagArgKey = LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key");
+ private static readonly Parser TagArgKey =
+ LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key");
// parser for a singular tag argument. Note that each TryQuoteOrChar will consume a whole quoted block before the Until() looks for whitespace
- private static readonly Parser TagArgParser = Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces);
+ private static readonly Parser TagArgParser =
+ Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces);
// parser for all tag arguments
- private static readonly Parser> TagArgsParser = TagArgParser.Until(TryLookTagEnd);
+ private static readonly Parser> TagArgsParser =
+ TagArgParser.Until(TryLookTagEnd);
// parser for an opening tag.
private static readonly Parser TryOpeningTag =
Try(Char('<'))
- .Then(SkipWhitespaces)
- .Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd)))
- .Select(string.Concat).Labelled($"opening tag");
+ .Then(SkipWhitespaces)
+ .Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd)))
+ .Select(string.Concat)
+ .Labelled("opening tag");
private static Parser> ParseTagArgs(string tag)
{
@@ -138,5 +193,6 @@ private static Parser TryTagTerminator(string tag)
.Then(TagEnd)
.Labelled($"closing {tag} tag");
}
+
#endregion
}
diff --git a/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs b/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs
index 53977eb636b..f900eec1eb4 100644
--- a/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs
+++ b/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs
@@ -21,14 +21,12 @@ protected override void Open()
{
base.Open();
- _window = this.CreateWindow();
+ _window = this.CreateWindowCenteredLeft();
_window.OnMarkingAdded += SendMarkingSet;
_window.OnMarkingRemoved += SendMarkingSet;
_window.OnMarkingColorChange += SendMarkingSetNoResend;
_window.OnMarkingRankChange += SendMarkingSet;
_window.OnLayerInfoModified += SendBaseLayer;
-
- _window.OpenCenteredLeft();
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs
index 320a9316bb6..e41066e6372 100644
--- a/Content.Client/Inventory/StrippableBoundUserInterface.cs
+++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs
@@ -64,11 +64,9 @@ protected override void Open()
{
base.Open();
- _strippingMenu = this.CreateWindow();
+ _strippingMenu = this.CreateWindowCenteredLeft();
_strippingMenu.OnDirty += UpdateMenu;
_strippingMenu.Title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan)));
-
- _strippingMenu?.OpenCenteredLeft();
}
protected override void Dispose(bool disposing)
@@ -190,9 +188,15 @@ private void SlotPressed(GUIBoundKeyEventArgs ev, SlotControl slot)
return;
if (ev.Function == ContentKeyFunctions.ExamineEntity)
+ {
_examine.DoExamine(slot.Entity.Value);
+ ev.Handle();
+ }
else if (ev.Function == EngineKeyFunctions.UseSecondary)
+ {
_ui.GetUIController().OpenVerbMenu(slot.Entity.Value);
+ ev.Handle();
+ }
}
private void AddInventoryButton(EntityUid invUid, string slotId, InventoryComponent inv)
diff --git a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
index 66f09aa41da..db99fb5daed 100644
--- a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
+++ b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
@@ -19,9 +19,8 @@ protected override void Open()
{
base.Open();
- _menu = this.CreateWindow();
+ _menu = this.CreateWindowCenteredRight();
_menu.SetEntity(Owner);
- _menu.OpenCenteredRight();
_menu.OnServerListButtonPressed += _ =>
{
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs
index 96658996642..1d2d7b995ff 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs
@@ -74,7 +74,7 @@ public void SetEntity(EntityUid uid)
if (_entityManager.TryGetComponent(Entity, out var latheComponent))
{
- if (!latheComponent.DynamicRecipes.Any())
+ if (!latheComponent.DynamicPacks.Any())
{
ServerListButton.Visible = false;
}
diff --git a/Content.Client/Light/HandheldLightSystem.cs b/Content.Client/Light/HandheldLightSystem.cs
index ddd99c7c483..d25b28756f8 100644
--- a/Content.Client/Light/HandheldLightSystem.cs
+++ b/Content.Client/Light/HandheldLightSystem.cs
@@ -21,6 +21,22 @@ public override void Initialize()
SubscribeLocalEvent(OnAppearanceChange);
}
+ ///
+ /// TODO: Not properly predicted yet. Don't call this function if you want a the actual return value!
+ ///
+ public override bool TurnOff(Entity ent, bool makeNoise = true)
+ {
+ return true;
+ }
+
+ ///
+ /// TODO: Not properly predicted yet. Don't call this function if you want a the actual return value!
+ ///
+ public override bool TurnOn(EntityUid user, Entity uid)
+ {
+ return true;
+ }
+
private void OnAppearanceChange(EntityUid uid, HandheldLightComponent? component, ref AppearanceChangeEvent args)
{
if (!Resolve(uid, ref component))
diff --git a/Content.Client/Light/RoofOverlay.cs b/Content.Client/Light/RoofOverlay.cs
index 5543103cdc6..0648f8624fa 100644
--- a/Content.Client/Light/RoofOverlay.cs
+++ b/Content.Client/Light/RoofOverlay.cs
@@ -1,10 +1,12 @@
using System.Numerics;
using Content.Shared.Light.Components;
+using Content.Shared.Light.EntitySystems;
using Content.Shared.Maps;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
+using Robust.Shared.Map.Enumerators;
using Robust.Shared.Physics;
namespace Content.Client.Light;
@@ -17,9 +19,9 @@ public sealed class RoofOverlay : Overlay
private readonly EntityLookupSystem _lookup;
private readonly SharedMapSystem _mapSystem;
+ private readonly SharedRoofSystem _roof = default!;
private readonly SharedTransformSystem _xformSystem;
- private readonly HashSet> _occluders = new();
private List> _grids = new();
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
@@ -33,6 +35,7 @@ public RoofOverlay(IEntityManager entManager)
_lookup = _entManager.System();
_mapSystem = _entManager.System();
+ _roof = _entManager.System();
_xformSystem = _entManager.System();
ZIndex = ContentZIndex;
@@ -86,29 +89,14 @@ protected override void Draw(in OverlayDrawArgs args)
worldHandle.SetTransform(matty);
var tileEnumerator = _mapSystem.GetTilesEnumerator(grid.Owner, grid, bounds);
+ var roofEnt = (grid.Owner, grid.Comp, roof);
// Due to stencilling we essentially draw on unrooved tiles
while (tileEnumerator.MoveNext(out var tileRef))
{
- if ((tileRef.Tile.Flags & (byte) TileFlag.Roof) == 0x0)
+ if (!_roof.IsRooved(roofEnt, tileRef.GridIndices))
{
- // Check if the tile is occluded in which case hide it anyway.
- // This is to avoid lit walls bleeding over to unlit tiles.
- _occluders.Clear();
- _lookup.GetLocalEntitiesIntersecting(grid.Owner, tileRef.GridIndices, _occluders);
- var found = false;
-
- foreach (var occluder in _occluders)
- {
- if (!occluder.Comp.Enabled)
- continue;
-
- found = true;
- break;
- }
-
- if (!found)
- continue;
+ continue;
}
var local = _lookup.GetLocalBounds(tileRef, grid.Comp.TileSize);
diff --git a/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs b/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs
index 8033d932f00..093191f51c8 100644
--- a/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs
+++ b/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs
@@ -128,7 +128,7 @@ private Animation BlinkingAnimation(PoweredLightVisualsComponent comp)
if (comp.BlinkingSound != null)
{
- var sound = _audio.GetSound(comp.BlinkingSound);
+ var sound = _audio.ResolveSound(comp.BlinkingSound);
blinkingAnim.AnimationTracks.Add(new AnimationTrackPlaySound()
{
KeyFrames =
diff --git a/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs b/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs
index 0b9d70207d7..a01129a480d 100644
--- a/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs
+++ b/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs
@@ -45,14 +45,14 @@ public CharacterPickerButton(
}
else
{
- var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
- var jod = prototypeManager.Index(highPriorityJob);
_previewDummy = UserInterfaceManager.GetUIController()
- .LoadProfileEntity(humanoid, jod, true, true);
+ .LoadProfileEntity(humanoid, null, true, true);
+ var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
if (highPriorityJob != default)
{
- description = $"{description}\n{jod.LocalizedName}";
+ var jobName = prototypeManager.Index(highPriorityJob).LocalizedName;
+ description = $"{description}\n{jobName}";
}
}
diff --git a/Content.Client/Mech/Ui/MechBoundUserInterface.cs b/Content.Client/Mech/Ui/MechBoundUserInterface.cs
index 558678718f1..6eeadea8e82 100644
--- a/Content.Client/Mech/Ui/MechBoundUserInterface.cs
+++ b/Content.Client/Mech/Ui/MechBoundUserInterface.cs
@@ -21,9 +21,8 @@ protected override void Open()
{
base.Open();
- _menu = this.CreateWindow();
+ _menu = this.CreateWindowCenteredLeft();
_menu.SetEntity(Owner);
- _menu.OpenCenteredLeft();
_menu.OnRemoveButtonPressed += uid =>
{
diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml
index 660f2e5e11f..dd40749d33b 100644
--- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml
+++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml
@@ -15,6 +15,9 @@
+
+
();
_spriteSystem = _entManager.System();
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
-
}
public void Set(string stationName, EntityUid? mapUid)
@@ -156,6 +157,11 @@ private void PopulateDepartmentList(IEnumerable departmentSens
// Populate departments
foreach (var sensor in departmentSensors)
{
+ if (!string.IsNullOrEmpty(SearchLineEdit.Text)
+ && !sensor.Name.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase)
+ && !sensor.Job.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase))
+ continue;
+
var coordinates = _entManager.GetCoordinates(sensor.Coordinates);
// Add a button that will hold a username and other details
@@ -285,7 +291,7 @@ private void PopulateDepartmentList(IEnumerable departmentSens
{
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
new NavMapBlip
- (coordinates.Value,
+ (CoordinatesToLocal(coordinates.Value),
_blipTexture,
(_trackedEntity == null || sensor.SuitSensorUid == _trackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
sensor.SuitSensorUid == _trackedEntity));
@@ -351,7 +357,7 @@ private void UpdateSensorsTable(NetEntity? currTrackedEntity, NetEntity? prevTra
if (NavMap.TrackedEntities.TryGetValue(castSensor.SuitSensorUid, out var data))
{
data = new NavMapBlip
- (data.Coordinates,
+ (CoordinatesToLocal(data.Coordinates),
data.Texture,
(currTrackedEntity == null || castSensor.SuitSensorUid == currTrackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
castSensor.SuitSensorUid == currTrackedEntity);
@@ -366,14 +372,11 @@ private void TryToScrollToFocus()
if (!_tryToScrollToListFocus)
return;
- if (!TryGetVerticalScrollbar(SensorScroller, out var vScrollbar))
- return;
-
if (TryGetNextScrollPosition(out float? nextScrollPosition))
{
- vScrollbar.ValueTarget = nextScrollPosition.Value;
+ SensorScroller.VScrollTarget = nextScrollPosition.Value;
- if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
+ if (MathHelper.CloseToPercent(SensorScroller.VScroll, SensorScroller.VScrollTarget))
{
_tryToScrollToListFocus = false;
return;
@@ -381,22 +384,6 @@ private void TryToScrollToFocus()
}
}
- private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
- {
- vScrollBar = null;
-
- foreach (var child in scroll.Children)
- {
- if (child is not VScrollBar)
- continue;
-
- vScrollBar = (VScrollBar) child;
- return true;
- }
-
- return false;
- }
-
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
{
nextScrollPosition = 0;
@@ -416,6 +403,26 @@ private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollP
return false;
}
+ ///
+ /// Converts the input coordinates to an EntityCoordinates which are in
+ /// reference to the grid that the map is displaying. This is a stylistic
+ /// choice; this window deliberately limits the rate that blips update,
+ /// but if the blip is attached to another grid which is moving, that
+ /// blip will move smoothly, unlike the others. By converting the
+ /// coordinates, we are back in control of the blip movement.
+ ///
+ private EntityCoordinates CoordinatesToLocal(EntityCoordinates refCoords)
+ {
+ if (NavMap.MapUid != null)
+ {
+ return _transformSystem.WithEntityId(refCoords, (EntityUid)NavMap.MapUid);
+ }
+ else
+ {
+ return refCoords;
+ }
+ }
+
private void ClearOutDatedData()
{
SensorsTable.RemoveAllChildren();
diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs
index ad69522dfda..b1a521433a8 100644
--- a/Content.Client/Overlays/StencilOverlay.Weather.cs
+++ b/Content.Client/Overlays/StencilOverlay.Weather.cs
@@ -1,4 +1,5 @@
using System.Numerics;
+using Content.Shared.Light.Components;
using Content.Shared.Weather;
using Robust.Client.Graphics;
using Robust.Shared.Map.Components;
@@ -34,11 +35,12 @@ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto,
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
var matty = Matrix3x2.Multiply(matrix, invMatrix);
worldHandle.SetTransform(matty);
+ _entManager.TryGetComponent(grid.Owner, out RoofComponent? roofComp);
foreach (var tile in _map.GetTilesIntersecting(grid.Owner, grid, worldAABB))
{
// Ignored tiles for stencil
- if (_weather.CanWeatherAffect(grid.Owner, grid, tile))
+ if (_weather.CanWeatherAffect(grid.Owner, grid, tile, roofComp))
{
continue;
}
diff --git a/Content.Client/Power/Generator/PortableGeneratorBoundUserInterface.cs b/Content.Client/Power/Generator/PortableGeneratorBoundUserInterface.cs
index 550e1041b62..9fa3b0c68f0 100644
--- a/Content.Client/Power/Generator/PortableGeneratorBoundUserInterface.cs
+++ b/Content.Client/Power/Generator/PortableGeneratorBoundUserInterface.cs
@@ -17,7 +17,7 @@ public PortableGeneratorBoundUserInterface(EntityUid owner, Enum uiKey) : base(o
protected override void Open()
{
base.Open();
- _window = this.CreateWindow();
+ _window = this.CreateWindowCenteredLeft();
_window.SetEntity(Owner);
_window.OnState += args =>
{
@@ -34,8 +34,6 @@ protected override void Open()
_window.OnPower += SetTargetPower;
_window.OnEjectFuel += EjectFuel;
_window.OnSwitchOutput += SwitchOutput;
-
- _window.OpenCenteredLeft();
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
index 85df10704dd..d3671e265ac 100644
--- a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
+++ b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
@@ -269,27 +269,6 @@ private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollP
return 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 void AutoScrollToFocus()
{
if (!_autoScrollActive)
@@ -299,15 +278,12 @@ private void AutoScrollToFocus()
if (scroll == null)
return;
- if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
- return;
-
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
return;
- vScrollbar.ValueTarget = nextScrollPosition.Value;
+ scroll.VScrollTarget = nextScrollPosition.Value;
- if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
+ if (MathHelper.CloseToPercent(scroll.VScroll, scroll.VScrollTarget))
_autoScrollActive = false;
}
@@ -464,11 +440,13 @@ public sealed class PowerMonitoringButton : Button
public BoxContainer MainContainer;
public TextureRect TextureRect;
public Label NameLocalized;
- public Label PowerValue;
+
public ProgressBar BatteryLevel;
public PanelContainer BackgroundPanel;
public Label BatteryPercentage;
+ public Label PowerValue;
+
public PowerMonitoringButton()
{
HorizontalExpand = true;
diff --git a/Content.Client/Salvage/UI/SalvageExpeditionConsoleBoundUserInterface.cs b/Content.Client/Salvage/UI/SalvageExpeditionConsoleBoundUserInterface.cs
index fe48b042f3e..1832f61c827 100644
--- a/Content.Client/Salvage/UI/SalvageExpeditionConsoleBoundUserInterface.cs
+++ b/Content.Client/Salvage/UI/SalvageExpeditionConsoleBoundUserInterface.cs
@@ -30,9 +30,8 @@ public SalvageExpeditionConsoleBoundUserInterface(EntityUid owner, Enum uiKey) :
protected override void Open()
{
base.Open();
- _window = this.CreateWindow();
+ _window = this.CreateWindowCenteredLeft();
_window.Title = Loc.GetString("salvage-expedition-window-title");
- _window.OpenCenteredLeft();
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs b/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
index d691f9acef3..a344ddd1eef 100644
--- a/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
+++ b/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
@@ -22,9 +22,8 @@ protected override void Open()
{
base.Open();
- _window = this.CreateWindow();
+ _window = this.CreateWindowCenteredLeft();
_window.Title = Loc.GetString("salvage-magnet-window-title");
- _window.OpenCenteredLeft();
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs b/Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs
index b8b4fb8a746..8d84abed8a5 100644
--- a/Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs
+++ b/Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs
@@ -21,10 +21,9 @@ protected override void Open()
{
base.Open();
- _window = this.CreateWindow();
+ _window = this.CreateWindowCenteredLeft();
_window.ShowIFF += SendIFFMessage;
_window.ShowVessel += SendVesselMessage;
- _window.OpenCenteredLeft();
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Tips/TippyUIController.cs b/Content.Client/Tips/TippyUIController.cs
index 7737a3d6982..77c10193a5d 100644
--- a/Content.Client/Tips/TippyUIController.cs
+++ b/Content.Client/Tips/TippyUIController.cs
@@ -104,7 +104,7 @@ private Vector2 UpdatePosition(TippyUI tippy, Vector2 screenSize, FrameEventArgs
? -WaddleRotation
: WaddleRotation;
- if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step))
+ if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step) && step.FootstepSoundCollection != null)
{
var audioParams = step.FootstepSoundCollection.Params
.AddVolume(-7f)
diff --git a/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs b/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs
index 44c92456a1f..4bea26b47b5 100644
--- a/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs
+++ b/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs
@@ -33,7 +33,7 @@ private void OnComponentInit(EntityUid uid, TimerTriggerVisualsComponent comp, C
{
comp.PrimingAnimation.AnimationTracks.Add(
new AnimationTrackPlaySound() {
- KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(comp.PrimingSound), 0) }
+ KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(comp.PrimingSound), 0) }
}
);
}
diff --git a/Content.Client/UserInterface/Controls/ListContainer.cs b/Content.Client/UserInterface/Controls/ListContainer.cs
index e1b3b948f04..0ee0a67af0a 100644
--- a/Content.Client/UserInterface/Controls/ListContainer.cs
+++ b/Content.Client/UserInterface/Controls/ListContainer.cs
@@ -96,9 +96,12 @@ public virtual void PopulateList(IReadOnlyList data)
{
ListContainerButton control = new(data[0], 0);
GenerateItem?.Invoke(data[0], control);
+ // Yes this AddChild is necessary for reasons (get proper style or whatever?)
+ // without it the DesiredSize may be different to the final DesiredSize.
+ AddChild(control);
control.Measure(Vector2Helpers.Infinity);
_itemHeight = control.DesiredSize.Y;
- control.Dispose();
+ control.Orphan();
}
// Ensure buttons are re-generated.
@@ -384,6 +387,7 @@ public sealed class ListContainerButton : ContainerButton, IEntityControl
public ListContainerButton(ListData data, int index)
{
+ AddStyleClass(StyleClassButton);
Data = data;
Index = index;
// AddChild(Background = new PanelContainer
diff --git a/Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs
index a7212934fd8..0f0564c5960 100644
--- a/Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs
+++ b/Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs
@@ -16,4 +16,9 @@ public VendingMachineItem(EntProtoId entProto, string text)
NameLabel.Text = text;
}
+
+ public void SetText(string text)
+ {
+ NameLabel.Text = text;
+ }
}
diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
index 3a3cc3aaf4b..84ca9d4ec7d 100644
--- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
+++ b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.IdentityManagement;
@@ -20,10 +21,15 @@ public sealed partial class VendingMachineMenu : FancyWindow
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly Dictionary _dummies = [];
+ private readonly Dictionary _listItems = new();
+ private readonly Dictionary _amounts = new();
- public event Action? OnItemSelected;
+ ///
+ /// Whether the vending machine is able to be interacted with or not.
+ ///
+ private bool _enabled;
- private readonly StyleBoxFlat _styleBox = new() { BackgroundColor = new Color(70, 73, 102) };
+ public event Action? OnItemSelected;
public VendingMachineMenu()
{
@@ -69,18 +75,23 @@ private void GenerateButton(ListData data, ListContainerButton button)
if (data is not VendorItemsListData { ItemProtoID: var protoID, ItemText: var text })
return;
- button.AddChild(new VendingMachineItem(protoID, text));
-
- button.ToolTip = text;
- button.StyleBoxOverride = _styleBox;
+ var item = new VendingMachineItem(protoID, text);
+ _listItems[protoID] = (button, item);
+ button.AddChild(item);
+ button.AddStyleClass("ButtonSquare");
+ button.Disabled = !_enabled || _amounts[protoID] == 0;
}
///
/// Populates the list of available items on the vending machine interface
/// and sets icons based on their prototypes
///
- public void Populate(List inventory)
+ public void Populate(List inventory, bool enabled)
{
+ _enabled = enabled;
+ _listItems.Clear();
+ _amounts.Clear();
+
if (inventory.Count == 0 && VendingContents.Visible)
{
SearchBar.Visible = false;
@@ -110,7 +121,10 @@ public void Populate(List inventory)
var entry = inventory[i];
if (!_prototypeManager.TryIndex(entry.ID, out var prototype))
+ {
+ _amounts[entry.ID] = 0;
continue;
+ }
if (!_dummies.TryGetValue(entry.ID, out var dummy))
{
@@ -130,11 +144,15 @@ public void Populate(List inventory)
}
var itemText = $"{itemName} [{entry.Amount}]";
+ _amounts[entry.ID] = entry.Amount;
if (itemText.Length > longestEntry.Length)
longestEntry = itemText;
- listData.Add(new VendorItemsListData(prototype.ID, itemText, i));
+ listData.Add(new VendorItemsListData(prototype.ID, i)
+ {
+ ItemText = itemText,
+ });
}
VendingContents.PopulateList(listData);
@@ -142,12 +160,43 @@ public void Populate(List inventory)
SetSizeAfterUpdate(longestEntry.Length, inventory.Count);
}
+ ///
+ /// Updates text entries for vending data in place without modifying the list controls.
+ ///
+ public void UpdateAmounts(List cachedInventory, bool enabled)
+ {
+ _enabled = enabled;
+
+ foreach (var proto in _dummies.Keys)
+ {
+ if (!_listItems.TryGetValue(proto, out var button))
+ continue;
+
+ var dummy = _dummies[proto];
+ var amount = cachedInventory.First(o => o.ID == proto).Amount;
+ // Could be better? Problem is all inventory entries get squashed.
+ var text = GetItemText(dummy, amount);
+
+ button.Item.SetText(text);
+ button.Button.Disabled = !enabled || amount == 0;
+ }
+ }
+
+ private string GetItemText(EntityUid dummy, uint amount)
+ {
+ var itemName = Identity.Name(dummy, _entityManager);
+ return $"{itemName} [{amount}]";
+ }
+
private void SetSizeAfterUpdate(int longestEntryLength, int contentCount)
{
SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400),
Math.Clamp(contentCount * 50, 150, 350));
}
}
-}
-public record VendorItemsListData(EntProtoId ItemProtoID, string ItemText, int ItemIndex) : ListData;
+ public record VendorItemsListData(EntProtoId ItemProtoID, int ItemIndex) : ListData
+ {
+ public string ItemText = string.Empty;
+ }
+}
diff --git a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
index 28b1b25adef..874808158d8 100644
--- a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
+++ b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
@@ -23,8 +23,7 @@ protected override void Open()
{
base.Open();
- _menu = this.CreateWindow();
- _menu.OpenCenteredLeft();
+ _menu = this.CreateWindowCenteredLeft();
_menu.Title = EntMan.GetComponent(Owner).EntityName;
_menu.OnItemSelected += OnItemSelected;
Refresh();
@@ -32,10 +31,21 @@ protected override void Open()
public void Refresh()
{
+ var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting;
+
var system = EntMan.System();
_cachedInventory = system.GetAllInventory(Owner);
- _menu?.Populate(_cachedInventory);
+ _menu?.Populate(_cachedInventory, enabled);
+ }
+
+ public void UpdateAmounts()
+ {
+ var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting;
+
+ var system = EntMan.System();
+ _cachedInventory = system.GetAllInventory(Owner);
+ _menu?.UpdateAmounts(_cachedInventory, enabled);
}
private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
@@ -54,7 +64,7 @@ private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
if (selectedItem == null)
return;
- SendMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
+ SendPredictedMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
}
protected override void Dispose(bool disposing)
diff --git a/Content.Client/VendingMachines/VendingMachineSystem.cs b/Content.Client/VendingMachines/VendingMachineSystem.cs
index 1b1dde2b67e..130296c8a12 100644
--- a/Content.Client/VendingMachines/VendingMachineSystem.cs
+++ b/Content.Client/VendingMachines/VendingMachineSystem.cs
@@ -1,6 +1,8 @@
+using System.Linq;
using Content.Shared.VendingMachines;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
+using Robust.Shared.GameStates;
namespace Content.Client.VendingMachines;
@@ -8,7 +10,6 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
{
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
- [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
public override void Initialize()
{
@@ -16,14 +17,69 @@ public override void Initialize()
SubscribeLocalEvent(OnAppearanceChange);
SubscribeLocalEvent(OnAnimationCompleted);
- SubscribeLocalEvent(OnVendingAfterState);
+ SubscribeLocalEvent(OnVendingHandleState);
}
- private void OnVendingAfterState(EntityUid uid, VendingMachineComponent component, ref AfterAutoHandleStateEvent args)
+ private void OnVendingHandleState(Entity entity, ref ComponentHandleState args)
{
- if (_uiSystem.TryGetOpenUi(uid, VendingMachineUiKey.Key, out var bui))
+ if (args.Current is not VendingMachineComponentState state)
+ return;
+
+ var uid = entity.Owner;
+ var component = entity.Comp;
+
+ component.Contraband = state.Contraband;
+ component.EjectEnd = state.EjectEnd;
+ component.DenyEnd = state.DenyEnd;
+ component.DispenseOnHitEnd = state.DispenseOnHitEnd;
+
+ // If all we did was update amounts then we can leave BUI buttons in place.
+ var fullUiUpdate = !component.Inventory.Keys.SequenceEqual(state.Inventory.Keys) ||
+ !component.EmaggedInventory.Keys.SequenceEqual(state.EmaggedInventory.Keys) ||
+ !component.ContrabandInventory.Keys.SequenceEqual(state.ContrabandInventory.Keys);
+
+ component.Inventory.Clear();
+ component.EmaggedInventory.Clear();
+ component.ContrabandInventory.Clear();
+
+ foreach (var entry in state.Inventory)
+ {
+ component.Inventory.Add(entry.Key, new(entry.Value));
+ }
+
+ foreach (var entry in state.EmaggedInventory)
+ {
+ component.EmaggedInventory.Add(entry.Key, new(entry.Value));
+ }
+
+ foreach (var entry in state.ContrabandInventory)
+ {
+ component.ContrabandInventory.Add(entry.Key, new(entry.Value));
+ }
+
+ if (UISystem.TryGetOpenUi(uid, VendingMachineUiKey.Key, out var bui))
+ {
+ if (fullUiUpdate)
+ {
+ bui.Refresh();
+ }
+ else
+ {
+ bui.UpdateAmounts();
+ }
+ }
+ }
+
+ protected override void UpdateUI(Entity entity)
+ {
+ if (!Resolve(entity, ref entity.Comp))
+ return;
+
+ if (UISystem.TryGetOpenUi(entity.Owner,
+ VendingMachineUiKey.Key,
+ out var bui))
{
- bui.Refresh();
+ bui.UpdateAmounts();
}
}
@@ -70,13 +126,13 @@ private void UpdateAppearance(EntityUid uid, VendingMachineVisualState visualSta
if (component.LoopDenyAnimation)
SetLayerState(VendingMachineVisualLayers.BaseUnshaded, component.DenyState, sprite);
else
- PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.DenyState, component.DenyDelay, sprite);
+ PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.DenyState, (float)component.DenyDelay.TotalSeconds, sprite);
SetLayerState(VendingMachineVisualLayers.Screen, component.ScreenState, sprite);
break;
case VendingMachineVisualState.Eject:
- PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.EjectState, component.EjectDelay, sprite);
+ PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.EjectState, (float)component.EjectDelay.TotalSeconds, sprite);
SetLayerState(VendingMachineVisualLayers.Screen, component.ScreenState, sprite);
break;
diff --git a/Content.Client/Weather/WeatherSystem.cs b/Content.Client/Weather/WeatherSystem.cs
index 975831392cb..26def25a15f 100644
--- a/Content.Client/Weather/WeatherSystem.cs
+++ b/Content.Client/Weather/WeatherSystem.cs
@@ -1,4 +1,5 @@
using System.Numerics;
+using Content.Shared.Light.Components;
using Content.Shared.Weather;
using Robust.Client.Audio;
using Robust.Client.GameObjects;
@@ -57,6 +58,7 @@ protected override void Run(EntityUid uid, WeatherData weather, WeatherPrototype
// Work out tiles nearby to determine volume.
if (TryComp(entXform.GridUid, out var grid))
{
+ TryComp(entXform.GridUid, out RoofComponent? roofComp);
var gridId = entXform.GridUid.Value;
// FloodFill to the nearest tile and use that for audio.
var seed = _mapSystem.GetTileRef(gridId, grid, entXform.Coordinates);
@@ -71,7 +73,7 @@ protected override void Run(EntityUid uid, WeatherData weather, WeatherPrototype
if (!visited.Add(node.GridIndices))
continue;
- if (!CanWeatherAffect(entXform.GridUid.Value, grid, node))
+ if (!CanWeatherAffect(entXform.GridUid.Value, grid, node, roofComp))
{
// Add neighbors
// TODO: Ideally we pick some deterministically random direction and use that
diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs
index 69d67798d77..7c395a76fcd 100644
--- a/Content.IntegrationTests/Tests/EntityTest.cs
+++ b/Content.IntegrationTests/Tests/EntityTest.cs
@@ -39,6 +39,7 @@ await server.WaitPost(() =>
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
+ .Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
.Select(p => p.ID)
.ToList();
@@ -101,6 +102,7 @@ await server.WaitPost(() =>
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
+ .Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
.Select(p => p.ID)
.ToList();
foreach (var protoId in protoIds)
@@ -345,6 +347,7 @@ public async Task AllComponentsOneToOneDeleteTest()
"DebugExceptionInitialize",
"DebugExceptionStartup",
"GridFill",
+ "RoomFill",
"Map", // We aren't testing a map entity in this test
"MapGrid",
"Broadphase",
diff --git a/Content.IntegrationTests/Tests/Lathe/LatheTest.cs b/Content.IntegrationTests/Tests/Lathe/LatheTest.cs
new file mode 100644
index 00000000000..2fe347f6362
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Lathe/LatheTest.cs
@@ -0,0 +1,133 @@
+using System.Collections.Generic;
+using System.Linq;
+using Content.Shared.Lathe;
+using Content.Shared.Materials;
+using Content.Shared.Prototypes;
+using Content.Shared.Research.Prototypes;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.IntegrationTests.Tests.Lathe;
+
+[TestFixture]
+public sealed class LatheTest
+{
+ [Test]
+ public async Task TestLatheRecipeIngredientsFitLathe()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var mapData = await pair.CreateTestMap();
+
+ var entMan = server.EntMan;
+ var protoMan = server.ProtoMan;
+ var compFactory = server.ResolveDependency();
+ var materialStorageSystem = server.System();
+ var whitelistSystem = server.System();
+ var latheSystem = server.System();
+
+ await server.WaitAssertion(() =>
+ {
+ // Find all the lathes
+ var latheProtos = protoMan.EnumeratePrototypes()
+ .Where(p => !p.Abstract)
+ .Where(p => !pair.IsTestPrototype(p))
+ .Where(p => p.HasComponent());
+
+ // Find every EntityPrototype that can be inserted into a MaterialStorage
+ var materialEntityProtos = protoMan.EnumeratePrototypes()
+ .Where(p => !p.Abstract)
+ .Where(p => !pair.IsTestPrototype(p))
+ .Where(p => p.HasComponent());
+
+ // Spawn all of the above material EntityPrototypes - we need actual entities to do whitelist checks
+ var materialEntities = new List(materialEntityProtos.Count());
+ foreach (var materialEntityProto in materialEntityProtos)
+ {
+ materialEntities.Add(entMan.SpawnEntity(materialEntityProto.ID, mapData.GridCoords));
+ }
+
+ Assert.Multiple(() =>
+ {
+ // Check each lathe individually
+ foreach (var latheProto in latheProtos)
+ {
+ if (!latheProto.TryGetComponent(out var latheComp, compFactory))
+ continue;
+
+ if (!latheProto.TryGetComponent(out var storageComp, compFactory))
+ continue;
+
+ // Test which material-containing entities are accepted by this lathe
+ var acceptedMaterials = new HashSet>();
+ foreach (var materialEntity in materialEntities)
+ {
+ Assert.That(entMan.TryGetComponent(materialEntity, out var compositionComponent));
+ if (whitelistSystem.IsWhitelistFail(storageComp.Whitelist, materialEntity))
+ continue;
+
+ // Mark the lathe as accepting each material in the entity
+ foreach (var (material, _) in compositionComponent.MaterialComposition)
+ {
+ acceptedMaterials.Add(material);
+ }
+ }
+
+ // Collect all possible recipes assigned to this lathe
+ var recipes = new HashSet>();
+ latheSystem.AddRecipesFromPacks(recipes, latheComp.StaticPacks);
+ latheSystem.AddRecipesFromPacks(recipes, latheComp.DynamicPacks);
+ if (latheProto.TryGetComponent(out var emagRecipesComp, compFactory))
+ {
+ latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagStaticPacks);
+ latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagDynamicPacks);
+ }
+
+ // Check each recipe assigned to this lathe
+ foreach (var recipeId in recipes)
+ {
+ Assert.That(protoMan.TryIndex(recipeId, out var recipeProto));
+
+ // Track the total material volume of the recipe
+ var totalQuantity = 0;
+ // Check each material called for by the recipe
+ foreach (var (materialId, quantity) in recipeProto.Materials)
+ {
+ Assert.That(protoMan.TryIndex(materialId, out var materialProto));
+ // Make sure the material is accepted by the lathe
+ Assert.That(acceptedMaterials, Does.Contain(materialId), $"Lathe {latheProto.ID} has recipe {recipeId} but does not accept any materials containing {materialId}");
+ totalQuantity += quantity;
+ }
+ // Make sure the recipe doesn't call for more material than the lathe can hold
+ if (storageComp.StorageLimit != null)
+ Assert.That(totalQuantity, Is.LessThanOrEqualTo(storageComp.StorageLimit), $"Lathe {latheProto.ID} has recipe {recipeId} which calls for {totalQuantity} units of materials but can only hold {storageComp.StorageLimit}");
+ }
+ }
+ });
+ });
+
+ await pair.CleanReturnAsync();
+ }
+
+ [Test]
+ public async Task AllLatheRecipesValidTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+
+ var server = pair.Server;
+ var proto = server.ProtoMan;
+
+ Assert.Multiple(() =>
+ {
+ foreach (var recipe in proto.EnumeratePrototypes())
+ {
+ if (recipe.Result == null)
+ Assert.That(recipe.ResultReagents, Is.Not.Null, $"Recipe '{recipe.ID}' has no result or result reagents.");
+ }
+ });
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/NPC/NPCTest.cs b/Content.IntegrationTests/Tests/NPC/NPCTest.cs
index 83321fe6138..064fd6c5bf0 100644
--- a/Content.IntegrationTests/Tests/NPC/NPCTest.cs
+++ b/Content.IntegrationTests/Tests/NPC/NPCTest.cs
@@ -45,6 +45,10 @@ private static void Count(HTNCompoundPrototype compound, Dictionary
var count = counts.GetOrNew(compound.ID);
count++;
+ // Compound tasks marked with AllowRecursion are only evaluated once
+ if (counts.ContainsKey(compound.ID) && compound.AllowRecursion)
+ continue;
+
Assert.That(count, Is.LessThan(50));
counts[compound.ID] = count;
Count(protoManager.Index(compoundTask.Task), counts, htnSystem, protoManager);
diff --git a/Content.IntegrationTests/Tests/ResearchTest.cs b/Content.IntegrationTests/Tests/ResearchTest.cs
index 7ae29a79ffd..1b8561ed1e7 100644
--- a/Content.IntegrationTests/Tests/ResearchTest.cs
+++ b/Content.IntegrationTests/Tests/ResearchTest.cs
@@ -2,6 +2,7 @@
using System.Linq;
using Content.Shared.Lathe;
using Content.Shared.Research.Prototypes;
+using Content.Shared.ReverseEngineering;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
@@ -52,13 +53,17 @@ public async Task AllTechPrintableTest()
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
+ var entMan = server.ResolveDependency();
var protoManager = server.ResolveDependency();
var compFact = server.ResolveDependency();
+ var latheSys = entMan.System();
+
await server.WaitAssertion(() =>
{
var allEnts = protoManager.EnumeratePrototypes();
- var allLathes = new HashSet();
+ var latheTechs = new HashSet>();
+ var unlockedTechs = new HashSet>();
foreach (var proto in allEnts)
{
if (proto.Abstract)
@@ -67,32 +72,35 @@ await server.WaitAssertion(() =>
if (pair.IsTestPrototype(proto))
continue;
+ if (proto.TryGetComponent(out var reverseEngineering, compFact) && reverseEngineering.Recipes != null)
+ unlockedTechs.UnionWith(reverseEngineering.Recipes);
+
if (!proto.TryGetComponent(out var lathe, compFact))
continue;
- allLathes.Add(lathe);
- }
- var latheTechs = new HashSet();
- foreach (var lathe in allLathes)
- {
- if (lathe.DynamicRecipes == null)
- continue;
+ latheSys.AddRecipesFromPacks(latheTechs, lathe.DynamicPacks);
- foreach (var recipe in lathe.DynamicRecipes)
- {
- latheTechs.Add(recipe);
- }
+ if (proto.TryGetComponent(out var emag, compFact))
+ latheSys.AddRecipesFromPacks(latheTechs, emag.EmagDynamicPacks);
}
Assert.Multiple(() =>
{
+ // check that every recipe a tech adds can be made on some lathe
foreach (var tech in protoManager.EnumeratePrototypes())
{
+ unlockedTechs.UnionWith(tech.RecipeUnlocks);
foreach (var recipe in tech.RecipeUnlocks)
{
- Assert.That(latheTechs, Does.Contain(recipe), $"Recipe \"{recipe}\" cannot be unlocked on any lathes.");
+ Assert.That(latheTechs, Does.Contain(recipe), $"Recipe '{recipe}' from tech '{tech.ID}' cannot be unlocked on any lathes.");
}
}
+
+ // now check that every dynamic recipe a lathe lists can be unlocked
+ foreach (var recipe in latheTechs)
+ {
+ Assert.That(unlockedTechs, Does.Contain(recipe), $"Recipe '{recipe}' is dynamic on a lathe but cannot be unlocked by research.");
+ }
});
});
diff --git a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs
index 74f3773ec5a..3b36224dd81 100644
--- a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs
+++ b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs
@@ -1,14 +1,14 @@
-using Content.Server.Advertise.Components;
using Content.Server.Chat.Systems;
+using Content.Shared.Advertise.Components;
+using Content.Shared.Advertise.Systems;
using Content.Shared.Chat;
-using Content.Shared.Dataset;
+using Content.Shared.UserInterface;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
-using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent;
-namespace Content.Server.Advertise;
+namespace Content.Server.Advertise.EntitySystems;
-public sealed partial class SpeakOnUIClosedSystem : EntitySystem
+public sealed partial class SpeakOnUIClosedSystem : SharedSpeakOnUIClosedSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -47,13 +47,4 @@ public bool TrySpeak(Entity entity)
entity.Comp.Flag = false;
return true;
}
-
- public bool TrySetFlag(Entity entity, bool value = true)
- {
- if (!Resolve(entity, ref entity.Comp))
- return false;
-
- entity.Comp.Flag = value;
- return true;
- }
}
diff --git a/Content.Server/AlertLevel/AlertLevelPrototype.cs b/Content.Server/AlertLevel/AlertLevelPrototype.cs
index c6740c16cc5..ccf6ff912c9 100644
--- a/Content.Server/AlertLevel/AlertLevelPrototype.cs
+++ b/Content.Server/AlertLevel/AlertLevelPrototype.cs
@@ -6,7 +6,7 @@ namespace Content.Server.AlertLevel;
[Prototype("alertLevels")]
public sealed partial class AlertLevelPrototype : IPrototype
{
- [IdDataField] public string ID { get; } = default!;
+ [IdDataField] public string ID { get; private set; } = default!;
///
/// Dictionary of alert levels. Keyed by string - the string key is the most important
diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs
index 04243535efc..b6d4d4c7e11 100644
--- a/Content.Server/Antag/AntagSelectionSystem.cs
+++ b/Content.Server/Antag/AntagSelectionSystem.cs
@@ -54,6 +54,8 @@ public override void Initialize()
{
base.Initialize();
+ Log.Level = LogLevel.Debug;
+
SubscribeLocalEvent(OnTakeGhostRole);
SubscribeLocalEvent(OnObjectivesTextGetInfo);
@@ -352,6 +354,8 @@ public void MakeAntag(Entity ent, ICommonSession? sessi
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
SendBriefing(session, def.Briefing);
+
+ Log.Debug($"Selected {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
}
var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def);
diff --git a/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs b/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs
index 9a551acc499..4b5e0679f54 100644
--- a/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs
+++ b/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs
@@ -28,7 +28,7 @@ public sealed partial class AntagRandomObjectivesComponent : Component
/// Difficulty is checked over all sets, but each set has its own probability and pick count.
///
[DataRecord]
-public record struct AntagObjectiveSet()
+public partial record struct AntagObjectiveSet()
{
///
/// The grouping used by the objective system to pick random objectives.
diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs
index b0bf3895092..a0e52e9b488 100644
--- a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs
+++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs
@@ -1,11 +1,9 @@
-using Content.Server.Power.Components;
using Content.Shared.UserInterface;
-using Content.Server.Advertise;
-using Content.Server.Advertise.Components;
+using Content.Server.Advertise.EntitySystems;
+using Content.Shared.Advertise.Components;
using Content.Shared.Arcade;
using Content.Shared.Power;
using Robust.Server.GameObjects;
-using Robust.Shared.Player;
namespace Content.Server.Arcade.BlockGame;
diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs
index 624f4cac3e8..1f622b6b8f0 100644
--- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs
+++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs
@@ -1,10 +1,10 @@
using Content.Server.Power.Components;
using Content.Shared.UserInterface;
-using Content.Server.Advertise;
-using Content.Server.Advertise.Components;
+using Content.Server.Advertise.EntitySystems;
+using Content.Shared.Advertise.Components;
+using Content.Shared.Arcade;
using Content.Shared.Mood;
using Content.Shared.Power;
-using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
@@ -25,7 +25,7 @@ public override void Initialize()
SubscribeLocalEvent(OnComponentInit);
SubscribeLocalEvent(OnAfterUIOpenSV);
- SubscribeLocalEvent(OnSVPlayerAction);
+ SubscribeLocalEvent(OnSVPlayerAction);
SubscribeLocalEvent(OnSVillainPower);
}
@@ -71,7 +71,7 @@ private void OnComponentInit(EntityUid uid, SpaceVillainArcadeComponent componen
component.RewardAmount = new Random().Next(component.RewardMinAmount, component.RewardMaxAmount + 1);
}
- private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent component, SpaceVillainArcadePlayerActionMessage msg)
+ private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent component, SharedSpaceVillainArcadeComponent.SpaceVillainArcadePlayerActionMessage msg)
{
if (component.Game == null)
return;
@@ -82,22 +82,22 @@ private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent compone
switch (msg.PlayerAction)
{
- case PlayerAction.Attack:
- case PlayerAction.Heal:
- case PlayerAction.Recharge:
+ case SharedSpaceVillainArcadeComponent.PlayerAction.Attack:
+ case SharedSpaceVillainArcadeComponent.PlayerAction.Heal:
+ case SharedSpaceVillainArcadeComponent.PlayerAction.Recharge:
component.Game.ExecutePlayerAction(uid, msg.PlayerAction, component);
// Any sort of gameplay action counts
if (TryComp(uid, out var speakComponent))
_speakOnUIClosed.TrySetFlag((uid, speakComponent));
break;
- case PlayerAction.NewGame:
+ case SharedSpaceVillainArcadeComponent.PlayerAction.NewGame:
_audioSystem.PlayPvs(component.NewGameSound, uid, AudioParams.Default.WithVolume(-4f));
component.Game = new SpaceVillainGame(uid, component, this);
- _uiSystem.ServerSendUiMessage(uid, SpaceVillainArcadeUiKey.Key, component.Game.GenerateMetaDataMessage());
+ _uiSystem.ServerSendUiMessage(uid, SharedSpaceVillainArcadeComponent.SpaceVillainArcadeUiKey.Key, component.Game.GenerateMetaDataMessage());
break;
- case PlayerAction.RequestData:
- _uiSystem.ServerSendUiMessage(uid, SpaceVillainArcadeUiKey.Key, component.Game.GenerateMetaDataMessage());
+ case SharedSpaceVillainArcadeComponent.PlayerAction.RequestData:
+ _uiSystem.ServerSendUiMessage(uid, SharedSpaceVillainArcadeComponent.SpaceVillainArcadeUiKey.Key, component.Game.GenerateMetaDataMessage());
break;
}
}
@@ -112,6 +112,6 @@ private void OnSVillainPower(EntityUid uid, SpaceVillainArcadeComponent componen
if (TryComp(uid, out var power) && power.Powered)
return;
- _uiSystem.CloseUi(uid, SpaceVillainArcadeUiKey.Key);
+ _uiSystem.CloseUi(uid, SharedSpaceVillainArcadeComponent.SpaceVillainArcadeUiKey.Key);
}
}
diff --git a/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs b/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs
index 643b0ce7823..a4e83594f20 100644
--- a/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs
+++ b/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs
@@ -17,8 +17,6 @@ public sealed partial class AirAlarmComponent : Component
// Remember to null this afterwards.
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
- [ViewVariables] public AirAlarmTab CurrentTab { get; set; }
-
public readonly HashSet KnownDevices = new();
public readonly Dictionary VentData = new();
public readonly Dictionary ScrubberData = new();
diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs
index 7ac4c653d3f..8f141de9de8 100644
--- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs
+++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs
@@ -131,6 +131,19 @@ private void SetThreshold(EntityUid uid, string address, AtmosMonitorThresholdTy
SyncDevice(uid, address);
}
+ private void SetAllThresholds(EntityUid uid, string address, AtmosSensorData data)
+ {
+ var payload = new NetworkPayload
+ {
+ [DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetAllThresholdsCmd,
+ [AtmosMonitorSystem.AtmosMonitorAllThresholdData] = data
+ };
+
+ _deviceNet.QueuePacket(uid, address, payload);
+
+ SyncDevice(uid, address);
+ }
+
///
/// Sync this air alarm's mode with the rest of the network.
///
@@ -174,7 +187,6 @@ public override void Initialize()
subs.Event(OnUpdateThreshold);
subs.Event(OnUpdateDeviceData);
subs.Event(OnCopyDeviceData);
- subs.Event(OnTabChange);
});
}
@@ -201,12 +213,6 @@ private void OnDeviceListUpdate(EntityUid uid, AirAlarmComponent component, Devi
SyncRegisterAllDevices(uid);
}
- private void OnTabChange(EntityUid uid, AirAlarmComponent component, AirAlarmTabSetMessage msg)
- {
- component.CurrentTab = msg.Tab;
- UpdateUI(uid, component);
- }
-
private void OnPowerChanged(EntityUid uid, AirAlarmComponent component, ref PowerChangedEvent args)
{
if (args.Powered)
@@ -348,6 +354,13 @@ private void OnCopyDeviceData(EntityUid uid, AirAlarmComponent component, AirAla
SetData(uid, addr, args.Data);
}
break;
+
+ case AtmosSensorData sensorData:
+ foreach (string addr in component.SensorData.Keys)
+ {
+ SetAllThresholds(uid, addr, sensorData);
+ }
+ break;
}
}
@@ -614,34 +627,19 @@ public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetwo
var pressure = CalculatePressureAverage(alarm);
var temperature = CalculateTemperatureAverage(alarm);
- var dataToSend = new Dictionary();
+ var dataToSend = new List<(string, IAtmosDeviceData)>();
- if (alarm.CurrentTab != AirAlarmTab.Settings)
+ foreach (var (addr, data) in alarm.VentData)
{
- switch (alarm.CurrentTab)
- {
- case AirAlarmTab.Vent:
- foreach (var (addr, data) in alarm.VentData)
- {
- dataToSend.Add(addr, data);
- }
-
- break;
- case AirAlarmTab.Scrubber:
- foreach (var (addr, data) in alarm.ScrubberData)
- {
- dataToSend.Add(addr, data);
- }
-
- break;
- case AirAlarmTab.Sensors:
- foreach (var (addr, data) in alarm.SensorData)
- {
- dataToSend.Add(addr, data);
- }
-
- break;
- }
+ dataToSend.Add((addr, data));
+ }
+ foreach (var (addr, data) in alarm.ScrubberData)
+ {
+ dataToSend.Add((addr, data));
+ }
+ foreach (var (addr, data) in alarm.SensorData)
+ {
+ dataToSend.Add((addr, data));
}
var deviceCount = alarm.KnownDevices.Count;
@@ -654,7 +652,7 @@ public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetwo
_ui.SetUiState(
uid,
SharedAirAlarmInterfaceKey.Key,
- new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, alarm.CurrentTab, highestAlarm.Value, alarm.AutoMode));
+ new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, highestAlarm.Value, alarm.AutoMode));
}
private const float Delay = 8f;
diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs
index 875d8ad1cda..3805c012b7f 100644
--- a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs
+++ b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs
@@ -33,10 +33,11 @@ public sealed class AtmosMonitorSystem : EntitySystem
// Commands
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
+ public const string AtmosMonitorSetAllThresholdsCmd = "atmos_monitor_set_all_thresholds";
// Packet data
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
-
+ public const string AtmosMonitorAllThresholdData = "atmos_monitor_all_threshold_data";
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
@@ -138,7 +139,12 @@ private void OnPacketRecv(EntityUid uid, AtmosMonitorComponent component, Device
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
}
-
+ break;
+ case AtmosMonitorSetAllThresholdsCmd:
+ if (args.Data.TryGetValue(AtmosMonitorAllThresholdData, out AtmosSensorData? allThresholdData))
+ {
+ SetAllThresholds(uid, allThresholdData);
+ }
break;
case AtmosDeviceNetworkSystem.SyncData:
var payload = new NetworkPayload();
@@ -403,4 +409,20 @@ public void SetThreshold(EntityUid uid, AtmosMonitorThresholdType type, AtmosAla
}
}
+
+ ///
+ /// Sets all of a monitor's thresholds at once according to the incoming
+ /// AtmosSensorData object's thresholds.
+ ///
+ /// The entity's uid
+ /// An AtmosSensorData object from which the thresholds will be loaded.
+ public void SetAllThresholds(EntityUid uid, AtmosSensorData allThresholdData)
+ {
+ SetThreshold(uid, AtmosMonitorThresholdType.Temperature, allThresholdData.TemperatureThreshold);
+ SetThreshold(uid, AtmosMonitorThresholdType.Pressure, allThresholdData.PressureThreshold);
+ foreach (var gas in Enum.GetValues())
+ {
+ SetThreshold(uid, AtmosMonitorThresholdType.Gas, allThresholdData.GasThresholds[gas], gas);
+ }
+ }
}
diff --git a/Content.Server/Audio/ServerGlobalSoundSystem.cs b/Content.Server/Audio/ServerGlobalSoundSystem.cs
index 3d30be8eeae..c54cafc8c53 100644
--- a/Content.Server/Audio/ServerGlobalSoundSystem.cs
+++ b/Content.Server/Audio/ServerGlobalSoundSystem.cs
@@ -19,9 +19,9 @@ public override void Shutdown()
_conHost.UnregisterCommand("playglobalsound");
}
- public void PlayAdminGlobal(Filter playerFilter, string filename, AudioParams? audioParams = null, bool replay = true)
+ public void PlayAdminGlobal(Filter playerFilter, ResolvedSoundSpecifier specifier, AudioParams? audioParams = null, bool replay = true)
{
- var msg = new AdminSoundEvent(filename, audioParams);
+ var msg = new AdminSoundEvent(specifier, audioParams);
RaiseNetworkEvent(msg, playerFilter, recordReplay: replay);
}
@@ -32,9 +32,9 @@ private Filter GetStationAndPvs(EntityUid source)
return stationFilter;
}
- public void PlayGlobalOnStation(EntityUid source, string filename, AudioParams? audioParams = null)
+ public void PlayGlobalOnStation(EntityUid source, ResolvedSoundSpecifier specifier, AudioParams? audioParams = null)
{
- var msg = new GameGlobalSoundEvent(filename, audioParams);
+ var msg = new GameGlobalSoundEvent(specifier, audioParams);
var filter = GetStationAndPvs(source);
RaiseNetworkEvent(msg, filter);
}
@@ -52,13 +52,13 @@ public void StopStationEventMusic(EntityUid source, StationEventMusicType type)
public void DispatchStationEventMusic(EntityUid source, SoundSpecifier sound, StationEventMusicType type)
{
- DispatchStationEventMusic(source, _audio.GetSound(sound), type);
+ DispatchStationEventMusic(source, _audio.ResolveSound(sound), type);
}
- public void DispatchStationEventMusic(EntityUid source, string sound, StationEventMusicType type)
+ public void DispatchStationEventMusic(EntityUid source, ResolvedSoundSpecifier specifier, StationEventMusicType type)
{
var audio = AudioParams.Default.WithVolume(-8);
- var msg = new StationEventMusicEvent(sound, type, audio);
+ var msg = new StationEventMusicEvent(specifier, type, audio);
var filter = GetStationAndPvs(source);
RaiseNetworkEvent(msg, filter);
diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs
index 871692b7dba..35c2490e4cb 100644
--- a/Content.Server/Botany/SeedPrototype.cs
+++ b/Content.Server/Botany/SeedPrototype.cs
@@ -14,7 +14,7 @@ namespace Content.Server.Botany;
[Prototype("seed")]
public sealed partial class SeedPrototype : SeedData, IPrototype
{
- [IdDataField] public string ID { get; private init; } = default!;
+ [IdDataField] public string ID { get; private set; } = default!;
}
public enum HarvestType : byte
@@ -296,12 +296,13 @@ public SeedData Clone()
CanScream = CanScream,
TurnIntoKudzu = TurnIntoKudzu,
SplatPrototype = SplatPrototype,
- Mutations = Mutations,
+ Mutations = new List(),
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
Unique = true,
};
+ newSeed.Mutations.AddRange(Mutations);
return newSeed;
}
diff --git a/Content.Server/Cargo/Components/PriceGunComponent.cs b/Content.Server/Cargo/Components/PriceGunComponent.cs
deleted file mode 100644
index 7207beae999..00000000000
--- a/Content.Server/Cargo/Components/PriceGunComponent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Server.Cargo.Components;
-
-///
-/// This is used for the price gun, which calculates the price of any object it appraises.
-///
-[RegisterComponent]
-public sealed partial class PriceGunComponent : Component
-{
-
-}
diff --git a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
index a7735787cbb..c650438b286 100644
--- a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
+++ b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs
@@ -12,15 +12,22 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
///
/// Maximum amount of bounties a station can have.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public int MaxBounties = 6;
///
/// A list of all the bounties currently active for a station.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public List Bounties = new();
+ ///
+ /// A list of all the bounties that have been completed or
+ /// skipped for a station.
+ ///
+ [DataField]
+ public List History = new();
+
///
/// Used to determine unique order IDs
///
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
index aca38858e49..a618f2b2a1d 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
@@ -8,6 +8,7 @@
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.Database;
+using Content.Shared.IdentityManagement;
using Content.Shared.NameIdentifier;
using Content.Shared.Paper;
using Content.Shared.Stacks;
@@ -16,6 +17,7 @@
using Robust.Server.Containers;
using Robust.Shared.Containers;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Cargo.Systems;
@@ -25,6 +27,7 @@ public sealed partial class CargoSystem
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
[ValidatePrototypeId]
private const string BountyNameIdentifierGroup = "Bounty";
@@ -54,7 +57,7 @@ private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent co
return;
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
+ _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, bountyDb.History, untilNextSkip));
}
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
@@ -95,13 +98,13 @@ private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent comp
return;
}
- if (!TryRemoveBounty(station, bounty.Value))
+ if (!TryRemoveBounty(station, bounty.Value, true, args.Actor))
return;
FillBountyDatabase(station);
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
+ _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
_audio.PlayPvs(component.SkipSound, uid);
}
@@ -179,7 +182,7 @@ private void OnSold(ref EntitySoldEvent args)
continue;
}
- TryRemoveBounty(station, bounty.Value);
+ TryRemoveBounty(station, bounty.Value, false);
FillBountyDatabase(station);
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled");
}
@@ -434,24 +437,44 @@ public bool TryAddBounty(EntityUid uid, CargoBountyPrototype bounty, StationCarg
}
[PublicAPI]
- public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null)
+ public bool TryRemoveBounty(Entity ent,
+ string dataId,
+ bool skipped,
+ EntityUid? actor = null)
{
- if (!TryGetBountyFromId(uid, dataId, out var data, component))
+ if (!TryGetBountyFromId(ent.Owner, dataId, out var data, ent.Comp))
return false;
- return TryRemoveBounty(uid, data.Value, component);
+ return TryRemoveBounty(ent, data.Value, skipped, actor);
}
- public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null)
+ public bool TryRemoveBounty(Entity ent,
+ CargoBountyData data,
+ bool skipped,
+ EntityUid? actor = null)
{
- if (!Resolve(uid, ref component))
+ if (!Resolve(ent, ref ent.Comp))
return false;
- for (var i = 0; i < component.Bounties.Count; i++)
+ for (var i = 0; i < ent.Comp.Bounties.Count; i++)
{
- if (component.Bounties[i].Id == data.Id)
+ if (ent.Comp.Bounties[i].Id == data.Id)
{
- component.Bounties.RemoveAt(i);
+ string? actorName = null;
+ if (actor != null)
+ {
+ var getIdentityEvent = new TryGetIdentityShortInfoEvent(ent.Owner, actor.Value);
+ RaiseLocalEvent(getIdentityEvent);
+ actorName = getIdentityEvent.Title;
+ }
+
+ ent.Comp.History.Add(new CargoBountyHistoryData(data,
+ skipped
+ ? CargoBountyHistoryData.BountyResult.Skipped
+ : CargoBountyHistoryData.BountyResult.Completed,
+ _gameTiming.CurTime,
+ actorName));
+ ent.Comp.Bounties.RemoveAt(i);
return true;
}
}
@@ -492,7 +515,7 @@ public void UpdateBountyConsoles()
}
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
+ _uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
}
}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs
index ed27780a843..44a56036965 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs
@@ -64,6 +64,7 @@ private void OnInteractUsing(EntityUid uid, CargoOrderConsoleComponent component
_audio.PlayPvs(component.ConfirmSound, uid);
UpdateBankAccount(stationUid.Value, bank, (int) price);
QueueDel(args.Used);
+ args.Handled = true;
}
private void OnInit(EntityUid uid, CargoOrderConsoleComponent orderConsole, ComponentInit args)
@@ -370,7 +371,7 @@ private void ConsolePopup(EntityUid actor, string text)
private void PlayDenySound(EntityUid uid, CargoOrderConsoleComponent component)
{
- _audio.PlayPvs(_audio.GetSound(component.ErrorSound), uid);
+ _audio.PlayPvs(_audio.ResolveSound(component.ErrorSound), uid);
}
private static CargoOrderData GetOrderData(CargoConsoleAddOrderMessage args, CargoProductPrototype cargoProduct, int id)
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs
index a78b6b1b25f..d56cb32b68c 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs
@@ -96,7 +96,7 @@ private void UpdateTelepad(float frameTime)
var currentOrder = comp.CurrentOrders.First();
if (FulfillOrder(currentOrder, xform.Coordinates, comp.PrinterOutput))
{
- _audio.PlayPvs(_audio.GetSound(comp.TeleportSound), uid, AudioParams.Default.WithVolume(-8f));
+ _audio.PlayPvs(_audio.ResolveSound(comp.TeleportSound), uid, AudioParams.Default.WithVolume(-8f));
if (_station.GetOwningStation(uid) is { } station)
UpdateOrders(station);
diff --git a/Content.Server/Cargo/Systems/PriceGunSystem.cs b/Content.Server/Cargo/Systems/PriceGunSystem.cs
index 19fe07bd253..5e7ab230606 100644
--- a/Content.Server/Cargo/Systems/PriceGunSystem.cs
+++ b/Content.Server/Cargo/Systems/PriceGunSystem.cs
@@ -1,73 +1,41 @@
-using Content.Server.Cargo.Components;
using Content.Server.Popups;
+using Content.Shared.Cargo.Components;
using Content.Shared.IdentityManagement;
-using Content.Shared.Interaction;
using Content.Shared.Timing;
-using Content.Shared.Verbs;
+using Content.Shared.Cargo.Systems;
+using Robust.Shared.Audio.Systems;
namespace Content.Server.Cargo.Systems;
-///
-/// This handles...
-///
-public sealed class PriceGunSystem : EntitySystem
+public sealed class PriceGunSystem : SharedPriceGunSystem
{
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly PricingSystem _pricingSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly CargoSystem _bountySystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
- ///
- public override void Initialize()
+ protected override bool GetPriceOrBounty(Entity entity, EntityUid target, EntityUid user)
{
- SubscribeLocalEvent(OnAfterInteract);
- SubscribeLocalEvent>(OnUtilityVerb);
- }
-
- private void OnUtilityVerb(EntityUid uid, PriceGunComponent component, GetVerbsEvent args)
- {
- if (!args.CanAccess || !args.CanInteract || args.Using == null)
- return;
-
- if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
- return;
-
- var price = _pricingSystem.GetPrice(args.Target);
-
- var verb = new UtilityVerb()
- {
- Act = () =>
- {
- _popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(args.Target, EntityManager)), ("price", $"{price:F2}")), args.User, args.User);
- _useDelay.TryResetDelay((uid, useDelay));
- },
- Text = Loc.GetString("price-gun-verb-text"),
- Message = Loc.GetString("price-gun-verb-message", ("object", Identity.Entity(args.Target, EntityManager)))
- };
-
- args.Verbs.Add(verb);
- }
-
- private void OnAfterInteract(EntityUid uid, PriceGunComponent component, AfterInteractEvent args)
- {
- if (!args.CanReach || args.Target == null || args.Handled)
- return;
-
- if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
- return;
-
+ if (!TryComp(entity.Owner, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((entity.Owner, useDelay)))
+ return false;
// Check if we're scanning a bounty crate
- if (_bountySystem.IsBountyComplete(args.Target.Value, out _))
+ if (_bountySystem.IsBountyComplete(target, out _))
{
- _popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), args.User, args.User);
+ _popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), user, user);
}
else // Otherwise appraise the price
{
- double price = _pricingSystem.GetPrice(args.Target.Value);
- _popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(args.Target.Value, EntityManager)), ("price", $"{price:F2}")), args.User, args.User);
+ var price = _pricingSystem.GetPrice(target);
+ _popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result",
+ ("object", Identity.Entity(target, EntityManager)),
+ ("price", $"{price:F2}")),
+ user,
+ user);
}
- _useDelay.TryResetDelay((uid, useDelay));
- args.Handled = true;
+ _audio.PlayPvs(entity.Comp.AppraisalSound, entity.Owner);
+ _useDelay.TryResetDelay((entity.Owner, useDelay));
+ return true;
}
}
diff --git a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs
index 98df7e2c503..8637e44e62a 100644
--- a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs
+++ b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs
@@ -427,6 +427,7 @@ private void OnLoaderUiMessage(EntityUid loaderUid, CartridgeLoaderComponent com
private void OnUiMessage(EntityUid uid, CartridgeLoaderComponent component, CartridgeUiMessage args)
{
var cartridgeEvent = args.MessageEvent;
+ cartridgeEvent.User = args.Actor;
cartridgeEvent.LoaderUid = GetNetEntity(uid);
cartridgeEvent.Actor = args.Actor;
diff --git a/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs b/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs
index 048fa777fc9..c5fe186d9bf 100644
--- a/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs
+++ b/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs
@@ -1,13 +1,22 @@
using Content.Shared.CartridgeLoader.Cartridges;
-using Content.Shared.DeltaV.CartridgeLoader.Cartridges; // DeltaV
+using Content.Shared.DeltaV.CartridgeLoader.Cartridges;
+using Content.Shared.Paper;
using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.CartridgeLoader.Cartridges;
-[RegisterComponent]
-[Access(typeof(LogProbeCartridgeSystem))]
+[RegisterComponent, Access(typeof(LogProbeCartridgeSystem))]
+[AutoGenerateComponentPause]
public sealed partial class LogProbeCartridgeComponent : Component
{
+ ///
+ /// The name of the scanned entity, sent to clients when they open the UI.
+ ///
+ [DataField]
+ public string EntityName = string.Empty;
+
///
/// The list of pulled access logs
///
@@ -20,9 +29,31 @@ public sealed partial class LogProbeCartridgeComponent : Component
[DataField, ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
+ ///
+ /// Paper to spawn when printing logs.
+ ///
+ [DataField]
+ public EntProtoId PaperPrototype = "PaperAccessLogs";
+
+ [DataField]
+ public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/diagnoser_printing.ogg");
+
+ ///
+ /// How long you have to wait before printing logs again.
+ ///
+ [DataField]
+ public TimeSpan PrintCooldown = TimeSpan.FromSeconds(5);
+
+ ///
+ /// When anyone is allowed to spawn another printout.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
+ public TimeSpan NextPrintAllowed = TimeSpan.Zero;
+
///
/// DeltaV: The last scanned NanoChat data, if any
///
[DataField]
public NanoChatData? ScannedNanoChatData;
}
+
diff --git a/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs
index 725901620d0..f26c6619b03 100644
--- a/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs
+++ b/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs
@@ -1,27 +1,42 @@
using Content.Shared.Access.Components;
+using Content.Shared.Administration.Logs;
using Content.Shared.Audio;
using Content.Shared.CartridgeLoader;
using Content.Shared.CartridgeLoader.Cartridges;
-using Content.Shared.DeltaV.NanoChat; // DeltaV
+using Content.Shared.Database;
+using Content.Shared.DeltaV.NanoChat;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Labels.EntitySystems;
+using Content.Shared.Paper;
using Content.Shared.Popups;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using System.Text;
namespace Content.Server.CartridgeLoader.Cartridges;
public sealed partial class LogProbeCartridgeSystem : EntitySystem // DeltaV - Made partial
{
+ [Dependency] private readonly CartridgeLoaderSystem _cartridge = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!;
- [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
- [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly SharedLabelSystem _label = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly PaperSystem _paper = default!;
public override void Initialize()
{
base.Initialize();
+
InitializeNanoChat(); // DeltaV
SubscribeLocalEvent(OnUiReady);
SubscribeLocalEvent(AfterInteract);
+ SubscribeLocalEvent(OnMessage);
}
///
@@ -48,9 +63,10 @@ private void AfterInteract(Entity ent, ref Cartridge
return;
//Play scanning sound with slightly randomized pitch
- _audioSystem.PlayEntity(ent.Comp.SoundScan, args.InteractEvent.User, target, AudioHelpers.WithVariation(0.25f, _random));
- _popupSystem.PopupCursor(Loc.GetString("log-probe-scan", ("device", target)), args.InteractEvent.User);
+ _audio.PlayEntity(ent.Comp.SoundScan, args.InteractEvent.User, target, AudioHelpers.WithVariation(0.25f, _random));
+ _popup.PopupCursor(Loc.GetString("log-probe-scan", ("device", target)), args.InteractEvent.User);
+ ent.Comp.EntityName = Name(target);
ent.Comp.PulledAccessLogs.Clear();
ent.Comp.ScannedNanoChatData = null; // DeltaV - Clear any previous NanoChat data
@@ -64,6 +80,9 @@ private void AfterInteract(Entity ent, ref Cartridge
ent.Comp.PulledAccessLogs.Add(log);
}
+ // Reverse the list so the oldest is at the bottom
+ ent.Comp.PulledAccessLogs.Reverse();
+
UpdateUiState(ent, args.Loader);
}
@@ -75,9 +94,49 @@ private void OnUiReady(Entity