diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 78544b608cc..7d515e52d1c 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,48 +1,34 @@
-
-
+
## About the PR
-
+
## Why / Balance
-
+
## How to test
## Media
-
+
## Requirements
-
-- [ ] I have read and I am following the [Pull Request Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html). I understand that not doing so may get my pr closed at maintainer’s discretion
-- [ ] I have added screenshots/videos to this PR showcasing its changes ingame, **or** this PR does not require an ingame showcase
+
+- [ ] I have read and am following the [Pull Request and Changelog Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
+- [ ] I have added media to this PR or it does not require an ingame showcase.
+
## Breaking changes
-
+
**Changelog**
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
new file mode 100644
index 00000000000..79bb66560e3
--- /dev/null
+++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
@@ -0,0 +1,215 @@
+using Content.Client.Stylesheets;
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.Components;
+using Content.Shared.Atmos.Monitor;
+using Content.Shared.FixedPoint;
+using Content.Shared.Temperature;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Map;
+using System.Linq;
+
+namespace Content.Client.Atmos.Consoles;
+
+[GenerateTypedNameReferences]
+public sealed partial class AtmosAlarmEntryContainer : BoxContainer
+{
+ public NetEntity NetEntity;
+ public EntityCoordinates? Coordinates;
+
+ private readonly IEntityManager _entManager;
+ private readonly IResourceCache _cache;
+
+ private Dictionary _alarmStrings = new Dictionary()
+ {
+ [AtmosAlarmType.Invalid] = "atmos-alerts-window-invalid-state",
+ [AtmosAlarmType.Normal] = "atmos-alerts-window-normal-state",
+ [AtmosAlarmType.Warning] = "atmos-alerts-window-warning-state",
+ [AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state",
+ };
+
+ private Dictionary _gasShorthands = new Dictionary()
+ {
+ [Gas.Ammonia] = "NH₃",
+ [Gas.CarbonDioxide] = "CO₂",
+ [Gas.Frezon] = "F",
+ [Gas.Nitrogen] = "N₂",
+ [Gas.NitrousOxide] = "N₂O",
+ [Gas.Oxygen] = "O₂",
+ [Gas.Plasma] = "P",
+ [Gas.Tritium] = "T",
+ [Gas.WaterVapor] = "H₂O",
+ };
+
+ public AtmosAlarmEntryContainer(NetEntity uid, EntityCoordinates? coordinates)
+ {
+ RobustXamlLoader.Load(this);
+
+ _entManager = IoCManager.Resolve();
+ _cache = IoCManager.Resolve();
+
+ NetEntity = uid;
+ Coordinates = coordinates;
+
+ // Load fonts
+ var headerFont = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Bold.ttf"), 11);
+ var normalFont = new VectorFont(_cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11);
+ var smallFont = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
+
+ // Set fonts
+ TemperatureHeaderLabel.FontOverride = headerFont;
+ PressureHeaderLabel.FontOverride = headerFont;
+ OxygenationHeaderLabel.FontOverride = headerFont;
+ GasesHeaderLabel.FontOverride = headerFont;
+
+ TemperatureLabel.FontOverride = normalFont;
+ PressureLabel.FontOverride = normalFont;
+ OxygenationLabel.FontOverride = normalFont;
+
+ NoDataLabel.FontOverride = headerFont;
+
+ SilenceCheckBox.Label.FontOverride = smallFont;
+ SilenceCheckBox.Label.FontColorOverride = Color.DarkGray;
+ }
+
+ public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlertsFocusDeviceData? focusData = null)
+ {
+ NetEntity = entry.NetEntity;
+ Coordinates = _entManager.GetCoordinates(entry.Coordinates);
+
+ // Load fonts
+ var normalFont = new VectorFont(_cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11);
+
+ // Update alarm state
+ if (!_alarmStrings.TryGetValue(entry.AlarmState, out var alarmString))
+ alarmString = "atmos-alerts-window-invalid-state";
+
+ AlarmStateLabel.Text = Loc.GetString(alarmString);
+ AlarmStateLabel.FontColorOverride = GetAlarmStateColor(entry.AlarmState);
+
+ // Update alarm name
+ AlarmNameLabel.Text = Loc.GetString("atmos-alerts-window-alarm-label", ("name", entry.EntityName), ("address", entry.Address));
+
+ // Focus updates
+ FocusContainer.Visible = isFocus;
+
+ if (isFocus)
+ SetAsFocus();
+ else
+ RemoveAsFocus();
+
+ if (isFocus && entry.Group == AtmosAlertsComputerGroup.AirAlarm)
+ {
+ MainDataContainer.Visible = (entry.AlarmState != AtmosAlarmType.Invalid);
+ NoDataLabel.Visible = (entry.AlarmState == AtmosAlarmType.Invalid);
+
+ if (focusData != null)
+ {
+ // Update temperature
+ var tempK = (FixedPoint2)focusData.Value.TemperatureData.Item1;
+ var tempC = (FixedPoint2)TemperatureHelpers.KelvinToCelsius(tempK.Float());
+
+ TemperatureLabel.Text = Loc.GetString("atmos-alerts-window-temperature-value", ("valueInC", tempC), ("valueInK", tempK));
+ TemperatureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.TemperatureData.Item2);
+
+ // Update pressure
+ PressureLabel.Text = Loc.GetString("atmos-alerts-window-pressure-value", ("value", (FixedPoint2)focusData.Value.PressureData.Item1));
+ PressureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.PressureData.Item2);
+
+ // Update oxygenation
+ var oxygenPercent = (FixedPoint2)0f;
+ var oxygenAlert = AtmosAlarmType.Invalid;
+
+ if (focusData.Value.GasData.TryGetValue(Gas.Oxygen, out var oxygenData))
+ {
+ oxygenPercent = oxygenData.Item2 * 100f;
+ oxygenAlert = oxygenData.Item3;
+ }
+
+ OxygenationLabel.Text = Loc.GetString("atmos-alerts-window-oxygenation-value", ("value", oxygenPercent));
+ OxygenationLabel.FontColorOverride = GetAlarmStateColor(oxygenAlert);
+
+ // Update other present gases
+ GasGridContainer.RemoveAllChildren();
+
+ var gasData = focusData.Value.GasData.Where(g => g.Key != Gas.Oxygen);
+
+ if (gasData.Count() == 0)
+ {
+ // No other gases
+ var gasLabel = new Label()
+ {
+ Text = Loc.GetString("atmos-alerts-window-other-gases-value-nil"),
+ FontOverride = normalFont,
+ FontColorOverride = StyleNano.DisabledFore,
+ HorizontalAlignment = HAlignment.Center,
+ VerticalAlignment = VAlignment.Center,
+ HorizontalExpand = true,
+ Margin = new Thickness(0, 2, 0, 0),
+ SetHeight = 24f,
+ };
+
+ GasGridContainer.AddChild(gasLabel);
+ }
+
+ else
+ {
+ // Add an entry for each gas
+ foreach ((var gas, (var mol, var percent, var alert)) in gasData)
+ {
+ var gasPercent = (FixedPoint2)0f;
+ gasPercent = percent * 100f;
+
+ if (!_gasShorthands.TryGetValue(gas, out var gasShorthand))
+ gasShorthand = "X";
+
+ var gasLabel = new Label()
+ {
+ Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasShorthand), ("value", gasPercent)),
+ FontOverride = normalFont,
+ FontColorOverride = GetAlarmStateColor(alert),
+ HorizontalAlignment = HAlignment.Center,
+ VerticalAlignment = VAlignment.Center,
+ HorizontalExpand = true,
+ Margin = new Thickness(0, 2, 0, 0),
+ SetHeight = 24f,
+ };
+
+ GasGridContainer.AddChild(gasLabel);
+ }
+ }
+ }
+ }
+ }
+
+ public void SetAsFocus()
+ {
+ FocusButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
+ ArrowTexture.TexturePath = "/Textures/Interface/Nano/inverted_triangle.svg.png";
+ }
+
+ public void RemoveAsFocus()
+ {
+ FocusButton.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen);
+ ArrowTexture.TexturePath = "/Textures/Interface/Nano/triangle_right.png";
+ FocusContainer.Visible = false;
+ }
+
+ private Color GetAlarmStateColor(AtmosAlarmType alarmType)
+ {
+ switch (alarmType)
+ {
+ case AtmosAlarmType.Normal:
+ return StyleNano.GoodGreenFore;
+ case AtmosAlarmType.Warning:
+ return StyleNano.ConcerningOrangeFore;
+ case AtmosAlarmType.Danger:
+ return StyleNano.DangerousRedFore;
+ }
+
+ return StyleNano.DisabledFore;
+ }
+}
diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs
new file mode 100644
index 00000000000..08cae979b9b
--- /dev/null
+++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs
@@ -0,0 +1,52 @@
+using Content.Shared.Atmos.Components;
+
+namespace Content.Client.Atmos.Consoles;
+
+public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables]
+ private AtmosAlertsComputerWindow? _menu;
+
+ public AtmosAlertsComputerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
+
+ protected override void Open()
+ {
+ _menu = new AtmosAlertsComputerWindow(this, Owner);
+ _menu.OpenCentered();
+ _menu.OnClose += Close;
+
+ EntMan.TryGetComponent(Owner, out var xform);
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(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);
+ }
+
+ public void SendFocusChangeMessage(NetEntity? netEntity)
+ {
+ SendMessage(new AtmosAlertsComputerFocusChangeMessage(netEntity));
+ }
+
+ public void SendDeviceSilencedMessage(NetEntity netEntity, bool silenceDevice)
+ {
+ SendMessage(new AtmosAlertsComputerDeviceSilencedMessage(netEntity, silenceDevice));
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _menu?.Dispose();
+ }
+}
diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
new file mode 100644
index 00000000000..8824a776ee6
--- /dev/null
+++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs
new file mode 100644
index 00000000000..f0b7ffbe119
--- /dev/null
+++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs
@@ -0,0 +1,548 @@
+using Content.Client.Message;
+using Content.Client.Pinpointer.UI;
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Atmos.Components;
+using Content.Shared.Atmos.Monitor;
+using Content.Shared.Pinpointer;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Map;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+
+namespace Content.Client.Atmos.Consoles;
+
+[GenerateTypedNameReferences]
+public sealed partial class AtmosAlertsComputerWindow : FancyWindow
+{
+ private readonly IEntityManager _entManager;
+ private readonly SpriteSystem _spriteSystem;
+
+ private EntityUid? _owner;
+ private NetEntity? _trackedEntity;
+
+ private AtmosAlertsComputerEntry[]? _airAlarms = null;
+ private AtmosAlertsComputerEntry[]? _fireAlarms = null;
+ private IEnumerable? _allAlarms = null;
+
+ private IEnumerable? _activeAlarms = null;
+ private Dictionary _deviceSilencingProgress = new();
+
+ public event Action? SendFocusChangeMessageAction;
+ public event Action? SendDeviceSilencedMessageAction;
+
+ private bool _autoScrollActive = false;
+ private bool _autoScrollAwaitsUpdate = false;
+
+ private const float SilencingDuration = 2.5f;
+
+ public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner)
+ {
+ RobustXamlLoader.Load(this);
+ _entManager = IoCManager.Resolve();
+ _spriteSystem = _entManager.System();
+
+ // Pass the owner to nav map
+ _owner = owner;
+ NavMap.Owner = _owner;
+
+ // Set nav map colors
+ NavMap.WallColor = new Color(64, 64, 64);
+ NavMap.TileColor = Color.DimGray * NavMap.WallColor;
+
+ // Set nav map grid uid
+ var stationName = Loc.GetString("atmos-alerts-window-unknown-location");
+
+ if (_entManager.TryGetComponent(owner, out var xform))
+ {
+ NavMap.MapUid = xform.GridUid;
+
+ // Assign station name
+ if (_entManager.TryGetComponent(xform.GridUid, out var stationMetaData))
+ stationName = stationMetaData.EntityName;
+
+ var msg = new FormattedMessage();
+ msg.TryAddMarkup(Loc.GetString("atmos-alerts-window-station-name", ("stationName", stationName)), out _);
+
+ StationName.SetMessage(msg);
+ }
+
+ else
+ {
+ StationName.SetMessage(stationName);
+ NavMap.Visible = false;
+ }
+
+ // Set trackable entity selected action
+ NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
+
+ // Update nav map
+ NavMap.ForceNavMapUpdate();
+
+ // Set tab container headers
+ MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts"));
+ MasterTabContainer.SetTabTitle(1, Loc.GetString("atmos-alerts-window-tab-air-alarms"));
+ MasterTabContainer.SetTabTitle(2, Loc.GetString("atmos-alerts-window-tab-fire-alarms"));
+
+ // Set UI toggles
+ ShowInactiveAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowInactiveAlarms, AtmosAlarmType.Invalid);
+ ShowNormalAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowNormalAlarms, AtmosAlarmType.Normal);
+ ShowWarningAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowWarningAlarms, AtmosAlarmType.Warning);
+ ShowDangerAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowDangerAlarms, AtmosAlarmType.Danger);
+
+ // Set atmos monitoring message action
+ SendFocusChangeMessageAction += userInterface.SendFocusChangeMessage;
+ SendDeviceSilencedMessageAction += userInterface.SendDeviceSilencedMessage;
+ }
+
+ #region Toggle handling
+
+ private void OnShowAlarmsToggled(CheckBox toggle, AtmosAlarmType toggledAlarmState)
+ {
+ if (_owner == null)
+ return;
+
+ if (!_entManager.TryGetComponent(_owner.Value, out var console))
+ return;
+
+ foreach (var device in console.AtmosDevices)
+ {
+ var alarmState = GetAlarmState(device.NetEntity);
+
+ if (toggledAlarmState != alarmState)
+ continue;
+
+ if (toggle.Pressed)
+ AddTrackedEntityToNavMap(device, alarmState);
+
+ else
+ NavMap.TrackedEntities.Remove(device.NetEntity);
+ }
+ }
+
+ private void OnSilenceAlertsToggled(NetEntity netEntity, bool toggleState)
+ {
+ if (!_entManager.TryGetComponent(_owner, out var console))
+ return;
+
+ if (toggleState)
+ _deviceSilencingProgress[netEntity] = SilencingDuration;
+
+ else
+ _deviceSilencingProgress.Remove(netEntity);
+
+ foreach (AtmosAlarmEntryContainer entryContainer in AlertsTable.Children)
+ {
+ if (entryContainer.NetEntity == netEntity)
+ entryContainer.SilenceAlarmProgressBar.Visible = toggleState;
+ }
+
+ SendDeviceSilencedMessageAction?.Invoke(netEntity, toggleState);
+ }
+
+ #endregion
+
+ public void UpdateUI(EntityCoordinates? consoleCoords, AtmosAlertsComputerEntry[] airAlarms, AtmosAlertsComputerEntry[] fireAlarms, AtmosAlertsFocusDeviceData? focusData)
+ {
+ if (_owner == null)
+ return;
+
+ if (!_entManager.TryGetComponent(_owner.Value, out var console))
+ return;
+
+ if (_trackedEntity != focusData?.NetEntity)
+ {
+ SendFocusChangeMessageAction?.Invoke(_trackedEntity);
+ focusData = null;
+ }
+
+ // Retain alarm data for use inbetween updates
+ _airAlarms = airAlarms;
+ _fireAlarms = fireAlarms;
+ _allAlarms = airAlarms.Concat(fireAlarms);
+
+ var silenced = console.SilencedDevices;
+
+ _activeAlarms = _allAlarms.Where(x => x.AlarmState > AtmosAlarmType.Normal &&
+ (!silenced.Contains(x.NetEntity) || _deviceSilencingProgress.ContainsKey(x.NetEntity)));
+
+ // Reset nav map data
+ NavMap.TrackedCoordinates.Clear();
+ NavMap.TrackedEntities.Clear();
+
+ // Add tracked entities to the nav map
+ foreach (var device in console.AtmosDevices)
+ {
+ if (!NavMap.Visible)
+ continue;
+
+ var alarmState = GetAlarmState(device.NetEntity);
+
+ if (_trackedEntity != device.NetEntity)
+ {
+ // Skip air alarms if the appropriate overlay is off
+ if (!ShowInactiveAlarms.Pressed && alarmState == AtmosAlarmType.Invalid)
+ continue;
+
+ if (!ShowNormalAlarms.Pressed && alarmState == AtmosAlarmType.Normal)
+ continue;
+
+ if (!ShowWarningAlarms.Pressed && alarmState == AtmosAlarmType.Warning)
+ continue;
+
+ if (!ShowDangerAlarms.Pressed && alarmState == AtmosAlarmType.Danger)
+ continue;
+ }
+
+ AddTrackedEntityToNavMap(device, alarmState);
+ }
+
+ // Show the monitor location
+ var consoleUid = _entManager.GetNetEntity(_owner);
+
+ if (consoleCoords != null && consoleUid != null)
+ {
+ var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
+ var blip = new NavMapBlip(consoleCoords.Value, texture, Color.Cyan, true, false);
+ NavMap.TrackedEntities[consoleUid.Value] = blip;
+ }
+
+ // Update the nav map
+ NavMap.ForceNavMapUpdate();
+
+ // Clear excess children from the tables
+ var activeAlarmCount = _activeAlarms.Count();
+
+ while (AlertsTable.ChildCount > activeAlarmCount)
+ AlertsTable.RemoveChild(AlertsTable.GetChild(AlertsTable.ChildCount - 1));
+
+ while (AirAlarmsTable.ChildCount > airAlarms.Length)
+ AirAlarmsTable.RemoveChild(AirAlarmsTable.GetChild(AirAlarmsTable.ChildCount - 1));
+
+ while (FireAlarmsTable.ChildCount > fireAlarms.Length)
+ FireAlarmsTable.RemoveChild(FireAlarmsTable.GetChild(FireAlarmsTable.ChildCount - 1));
+
+ // Update all entries in each table
+ for (int index = 0; index < _activeAlarms.Count(); index++)
+ {
+ var entry = _activeAlarms.ElementAt(index);
+ UpdateUIEntry(entry, index, AlertsTable, console, focusData);
+ }
+
+ for (int index = 0; index < airAlarms.Count(); index++)
+ {
+ var entry = airAlarms.ElementAt(index);
+ UpdateUIEntry(entry, index, AirAlarmsTable, console, focusData);
+ }
+
+ for (int index = 0; index < fireAlarms.Count(); index++)
+ {
+ var entry = fireAlarms.ElementAt(index);
+ UpdateUIEntry(entry, index, FireAlarmsTable, console, focusData);
+ }
+
+ // If no alerts are active, display a message
+ if (MasterTabContainer.CurrentTab == 0 && activeAlarmCount == 0)
+ {
+ var label = new RichTextLabel()
+ {
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ HorizontalAlignment = HAlignment.Center,
+ VerticalAlignment = VAlignment.Center,
+ };
+
+ label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", StyleNano.GoodGreenFore.ToHexNoAlpha())));
+
+ AlertsTable.AddChild(label);
+ }
+
+ // Update the alerts tab with the number of active alerts
+ if (activeAlarmCount == 0)
+ MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts"));
+
+ else
+ MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount)));
+
+ // Auto-scroll re-enable
+ if (_autoScrollAwaitsUpdate)
+ {
+ _autoScrollActive = true;
+ _autoScrollAwaitsUpdate = false;
+ }
+ }
+
+ private void AddTrackedEntityToNavMap(AtmosAlertsDeviceNavMapData metaData, AtmosAlarmType alarmState)
+ {
+ var data = GetBlipTexture(alarmState);
+
+ if (data == null)
+ return;
+
+ var texture = data.Value.Item1;
+ var color = data.Value.Item2;
+ var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
+
+ if (_trackedEntity != null && _trackedEntity != metaData.NetEntity)
+ color *= Color.DimGray;
+
+ var selectable = true;
+ var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable);
+
+ NavMap.TrackedEntities[metaData.NetEntity] = blip;
+ }
+
+ private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null)
+ {
+ // Make new UI entry if required
+ if (index >= table.ChildCount)
+ {
+ var newEntryContainer = new AtmosAlarmEntryContainer(entry.NetEntity, _entManager.GetCoordinates(entry.Coordinates));
+
+ // On click
+ newEntryContainer.FocusButton.OnButtonUp += args =>
+ {
+ if (_trackedEntity == newEntryContainer.NetEntity)
+ {
+ _trackedEntity = null;
+ }
+
+ else
+ {
+ _trackedEntity = newEntryContainer.NetEntity;
+
+ if (newEntryContainer.Coordinates != null)
+ NavMap.CenterToCoordinates(newEntryContainer.Coordinates.Value);
+ }
+
+ // Send message to console that the focus has changed
+ SendFocusChangeMessageAction?.Invoke(_trackedEntity);
+
+ // Update affected UI elements across all tables
+ UpdateConsoleTable(console, AlertsTable, _trackedEntity);
+ UpdateConsoleTable(console, AirAlarmsTable, _trackedEntity);
+ UpdateConsoleTable(console, FireAlarmsTable, _trackedEntity);
+ };
+
+ // On toggling the silence check box
+ newEntryContainer.SilenceCheckBox.OnToggled += _ => OnSilenceAlertsToggled(newEntryContainer.NetEntity, newEntryContainer.SilenceCheckBox.Pressed);
+
+ // Add the entry to the current table
+ table.AddChild(newEntryContainer);
+ }
+
+ // Update values and UI elements
+ var tableChild = table.GetChild(index);
+
+ if (tableChild is not AtmosAlarmEntryContainer)
+ {
+ table.RemoveChild(tableChild);
+ UpdateUIEntry(entry, index, table, console, focusData);
+
+ return;
+ }
+
+ var entryContainer = (AtmosAlarmEntryContainer)tableChild;
+
+ entryContainer.UpdateEntry(entry, entry.NetEntity == _trackedEntity, focusData);
+
+ if (_trackedEntity != entry.NetEntity)
+ {
+ var silenced = console.SilencedDevices;
+ entryContainer.SilenceCheckBox.Pressed = (silenced.Contains(entry.NetEntity) || _deviceSilencingProgress.ContainsKey(entry.NetEntity));
+ }
+
+ entryContainer.SilenceAlarmProgressBar.Visible = (table == AlertsTable && _deviceSilencingProgress.ContainsKey(entry.NetEntity));
+ }
+
+ private void UpdateConsoleTable(AtmosAlertsComputerComponent console, Control table, NetEntity? currTrackedEntity)
+ {
+ foreach (var tableChild in table.Children)
+ {
+ if (tableChild is not AtmosAlarmEntryContainer)
+ continue;
+
+ var entryContainer = (AtmosAlarmEntryContainer)tableChild;
+
+ if (entryContainer.NetEntity != currTrackedEntity)
+ entryContainer.RemoveAsFocus();
+
+ else if (entryContainer.NetEntity == currTrackedEntity)
+ entryContainer.SetAsFocus();
+ }
+ }
+
+ private void SetTrackedEntityFromNavMap(NetEntity? netEntity)
+ {
+ if (netEntity == null)
+ return;
+
+ if (!_entManager.TryGetComponent(_owner, out var console))
+ return;
+
+ _trackedEntity = netEntity;
+
+ if (netEntity != null)
+ {
+ // Tab switching
+ if (MasterTabContainer.CurrentTab != 0 || _activeAlarms?.Any(x => x.NetEntity == netEntity) == false)
+ {
+ var device = console.AtmosDevices.FirstOrNull(x => x.NetEntity == netEntity);
+
+ switch (device?.Group)
+ {
+ case AtmosAlertsComputerGroup.AirAlarm:
+ MasterTabContainer.CurrentTab = 1; break;
+ case AtmosAlertsComputerGroup.FireAlarm:
+ MasterTabContainer.CurrentTab = 2; break;
+ }
+ }
+
+ // Get the scroll position of the selected entity on the selected button the UI
+ ActivateAutoScrollToFocus();
+ }
+
+ // Send message to console that the focus has changed
+ SendFocusChangeMessageAction?.Invoke(_trackedEntity);
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ AutoScrollToFocus();
+
+ // Device silencing update
+ foreach ((var device, var remainingTime) in _deviceSilencingProgress)
+ {
+ var t = remainingTime - args.DeltaSeconds;
+
+ if (t <= 0)
+ {
+ _deviceSilencingProgress.Remove(device);
+
+ if (device == _trackedEntity)
+ _trackedEntity = null;
+ }
+
+ else
+ _deviceSilencingProgress[device] = t;
+ }
+ }
+
+ private void ActivateAutoScrollToFocus()
+ {
+ _autoScrollActive = false;
+ _autoScrollAwaitsUpdate = true;
+ }
+
+ private void AutoScrollToFocus()
+ {
+ if (!_autoScrollActive)
+ return;
+
+ var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
+ if (scroll == null)
+ return;
+
+ if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
+ return;
+
+ if (!TryGetNextScrollPosition(out float? nextScrollPosition))
+ return;
+
+ vScrollbar.ValueTarget = nextScrollPosition.Value;
+
+ if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
+ _autoScrollActive = false;
+ }
+
+ private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
+ {
+ vScrollBar = null;
+
+ foreach (var child in scroll.Children)
+ {
+ if (child is not VScrollBar)
+ continue;
+
+ var castChild = child as VScrollBar;
+
+ if (castChild != null)
+ {
+ vScrollBar = castChild;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
+ {
+ nextScrollPosition = null;
+
+ var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
+ if (scroll == null)
+ return false;
+
+ var container = scroll.Children.ElementAt(0) as BoxContainer;
+ if (container == null || container.Children.Count() == 0)
+ return false;
+
+ // Exit if the heights of the children haven't been initialized yet
+ if (!container.Children.Any(x => x.Height > 0))
+ return false;
+
+ nextScrollPosition = 0;
+
+ foreach (var control in container.Children)
+ {
+ if (control == null || control is not AtmosAlarmEntryContainer)
+ continue;
+
+ if (((AtmosAlarmEntryContainer)control).NetEntity == _trackedEntity)
+ return true;
+
+ nextScrollPosition += control.Height;
+ }
+
+ // Failed to find control
+ nextScrollPosition = null;
+
+ return false;
+ }
+
+ private AtmosAlarmType GetAlarmState(NetEntity netEntity)
+ {
+ var alarmState = _allAlarms?.FirstOrNull(x => x.NetEntity == netEntity)?.AlarmState;
+
+ if (alarmState == null)
+ return AtmosAlarmType.Invalid;
+
+ return alarmState.Value;
+ }
+
+ private (SpriteSpecifier.Texture, Color)? GetBlipTexture(AtmosAlarmType alarmState)
+ {
+ (SpriteSpecifier.Texture, Color)? output = null;
+
+ switch (alarmState)
+ {
+ case AtmosAlarmType.Invalid:
+ output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), StyleNano.DisabledFore); break;
+ case AtmosAlarmType.Normal:
+ output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), Color.LimeGreen); break;
+ case AtmosAlarmType.Warning:
+ output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), new Color(255, 182, 72)); break;
+ case AtmosAlarmType.Danger:
+ output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), new Color(255, 67, 67)); break;
+ }
+
+ return output;
+ }
+}
diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
index 2ae15188355..d9e94e373b4 100644
--- a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
+++ b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
@@ -30,7 +30,6 @@ protected override void Open()
_window.AirAlarmModeChanged += OnAirAlarmModeChanged;
_window.AutoModeChanged += OnAutoModeChanged;
_window.ResyncAllRequested += ResyncAllDevices;
- _window.AirAlarmTabChange += OnTabChanged;
}
private void ResyncAllDevices()
@@ -63,11 +62,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);
diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
index eeec11c7660..e1425ac491b 100644
--- a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
@@ -23,7 +23,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;
@@ -80,11 +79,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 +111,6 @@ public void UpdateState(AirAlarmUIState state)
{
UpdateDeviceData(addr, dev);
}
-
- _tabContainer.CurrentTab = (int) state.Tab;
}
public void UpdateModeSelector(AirAlarmMode mode)
diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs
index ca6336b91b8..b525747aa9c 100644
--- a/Content.Client/Audio/AmbientSoundSystem.cs
+++ b/Content.Client/Audio/AmbientSoundSystem.cs
@@ -306,6 +306,9 @@ private void ProcessNearbyAmbience(TransformComponent playerXform)
.WithMaxDistance(comp.Range);
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
+ if (stream == null)
+ continue;
+
_playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key);
playingCount++;
diff --git a/Content.Client/Audio/ClientGlobalSoundSystem.cs b/Content.Client/Audio/ClientGlobalSoundSystem.cs
index 7c77865f741..50c3971d95a 100644
--- a/Content.Client/Audio/ClientGlobalSoundSystem.cs
+++ b/Content.Client/Audio/ClientGlobalSoundSystem.cs
@@ -67,7 +67,7 @@ private void PlayAdminSound(AdminSoundEvent soundEvent)
if(!_adminAudioEnabled) return;
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
- _adminAudio.Add(stream.Value.Entity);
+ _adminAudio.Add(stream?.Entity);
}
private void PlayStationEventMusic(StationEventMusicEvent soundEvent)
@@ -76,7 +76,7 @@ private void PlayStationEventMusic(StationEventMusicEvent soundEvent)
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
- _eventAudio.Add(soundEvent.Type, stream.Value.Entity);
+ _eventAudio.Add(soundEvent.Type, stream?.Entity);
}
private void PlayGameSound(GameGlobalSoundEvent soundEvent)
diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
index d60c978ccf5..bf7ab26cba2 100644
--- a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
+++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
@@ -213,9 +213,9 @@ private void UpdateAmbientMusic()
false,
AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
- _ambientMusicStream = strim.Value.Entity;
+ _ambientMusicStream = strim?.Entity;
- if (_musicProto.FadeIn)
+ if (_musicProto.FadeIn && strim != null)
{
FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime);
}
diff --git a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
index 92c5b7a4191..9864dbcb2a9 100644
--- a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
+++ b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
@@ -185,7 +185,7 @@ private void PlaySoundtrack(string soundtrackFilename)
false,
_lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)))
);
- if (playResult.Value.Entity == default)
+ if (playResult == null)
{
_sawmill.Warning(
$"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!",
diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs
index 7092a4b2860..b2092cd50f6 100644
--- a/Content.Client/Buckle/BuckleSystem.cs
+++ b/Content.Client/Buckle/BuckleSystem.cs
@@ -16,7 +16,6 @@ public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnHandleState);
SubscribeLocalEvent(OnAppearanceChange);
SubscribeLocalEvent(OnStrapMoveEvent);
}
@@ -61,21 +60,6 @@ private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveE
}
}
- private void OnHandleState(Entity ent, ref ComponentHandleState args)
- {
- if (args.Current is not BuckleState state)
- return;
-
- ent.Comp.DontCollide = state.DontCollide;
- ent.Comp.BuckleTime = state.BuckleTime;
- var strapUid = EnsureEntity(state.BuckledTo, ent);
-
- SetBuckledTo(ent, strapUid == null ? null : new (strapUid.Value, null));
-
- var (uid, component) = ent;
-
- }
-
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
{
if (!TryComp(uid, out var rotVisuals))
diff --git a/Content.Client/CartridgeLoader/Cartridges/CrewManifestUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/CrewManifestUiFragment.xaml.cs
index 273707cb6ea..27ddd51815e 100644
--- a/Content.Client/CartridgeLoader/Cartridges/CrewManifestUiFragment.xaml.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/CrewManifestUiFragment.xaml.cs
@@ -1,4 +1,5 @@
-using Content.Shared.CrewManifest;
+using Content.Client.CrewManifest.UI;
+using Content.Shared.CrewManifest;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
diff --git a/Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml
index bd5879408ef..fd5a7bb1617 100644
--- a/Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml
+++ b/Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml
@@ -26,7 +26,7 @@
Text="{Loc 'news-read-ui-next-text'}"
ToolTip="{Loc 'news-read-ui-next-tooltip'}"/>
-
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs b/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs
new file mode 100644
index 00000000000..3c97b8b37d1
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs
@@ -0,0 +1,30 @@
+using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+public sealed partial class WantedListUi : UIFragment
+{
+ private WantedListUiFragment? _fragment;
+
+ public override Control GetUIFragmentRoot()
+ {
+ return _fragment!;
+ }
+
+ public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
+ {
+ _fragment = new WantedListUiFragment();
+ }
+
+ public override void UpdateState(BoundUserInterfaceState state)
+ {
+ switch (state)
+ {
+ case WantedListUiState cast:
+ _fragment?.UpdateState(cast.Records);
+ break;
+ }
+ }
+}
diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs
new file mode 100644
index 00000000000..4137f6c2af0
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs
@@ -0,0 +1,240 @@
+using System.Linq;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.CriminalRecords.Systems;
+using Content.Shared.Security;
+using Content.Shared.StatusIcon;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Input;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class WantedListUiFragment : BoxContainer
+{
+ [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ private readonly SpriteSystem _spriteSystem;
+
+ private string? _selectedTargetName;
+ private List _wantedRecords = new();
+
+ public WantedListUiFragment()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _spriteSystem = _entitySystem.GetEntitySystem();
+
+ SearchBar.OnTextChanged += OnSearchBarTextChanged;
+ }
+
+ private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
+ {
+ var found = !String.IsNullOrWhiteSpace(args.Text)
+ ? _wantedRecords.FindAll(r =>
+ r.TargetInfo.Name.Contains(args.Text) ||
+ r.Status.ToString().Contains(args.Text, StringComparison.OrdinalIgnoreCase))
+ : _wantedRecords;
+
+ UpdateState(found, false);
+ }
+
+ public void UpdateState(List records, bool refresh = true)
+ {
+ if (records.Count == 0)
+ {
+ NoRecords.Visible = true;
+ RecordsList.Visible = false;
+ RecordUnselected.Visible = false;
+ PersonContainer.Visible = false;
+
+ _selectedTargetName = null;
+ if (refresh)
+ _wantedRecords.Clear();
+
+ RecordsList.PopulateList(new List());
+
+ return;
+ }
+
+ NoRecords.Visible = false;
+ RecordsList.Visible = true;
+ RecordUnselected.Visible = true;
+ PersonContainer.Visible = false;
+
+ var dataList = records.Select(r => new StatusListData(r)).ToList();
+
+ RecordsList.GenerateItem = GenerateItem;
+ RecordsList.ItemPressed = OnItemSelected;
+ RecordsList.PopulateList(dataList);
+
+ if (refresh)
+ _wantedRecords = records;
+ }
+
+ private void OnItemSelected(BaseButton.ButtonEventArgs args, ListData data)
+ {
+ if (data is not StatusListData(var record))
+ return;
+
+ FormattedMessage GetLoc(string fluentId, params (string,object)[] args)
+ {
+ var msg = new FormattedMessage();
+ var fluent = Loc.GetString(fluentId, args);
+ msg.AddMarkupPermissive(fluent);
+ return msg;
+ }
+
+ // Set personal info
+ PersonName.Text = record.TargetInfo.Name;
+ TargetAge.SetMessage(GetLoc(
+ "wanted-list-age-label",
+ ("age", record.TargetInfo.Age)
+ ));
+ TargetJob.SetMessage(GetLoc(
+ "wanted-list-job-label",
+ ("job", record.TargetInfo.JobTitle.ToLower())
+ ));
+ TargetSpecies.SetMessage(GetLoc(
+ "wanted-list-species-label",
+ ("species", record.TargetInfo.Species.ToLower())
+ ));
+ TargetGender.SetMessage(GetLoc(
+ "wanted-list-gender-label",
+ ("gender", record.TargetInfo.Gender)
+ ));
+
+ // Set reason
+ WantedReason.SetMessage(GetLoc(
+ "wanted-list-reason-label",
+ ("reason", record.Reason ?? Loc.GetString("wanted-list-unknown-reason-label"))
+ ));
+
+ // Set status
+ PersonState.SetMessage(GetLoc(
+ "wanted-list-status-label",
+ ("status", record.Status.ToString().ToLower())
+ ));
+
+ // Set initiator
+ InitiatorName.SetMessage(GetLoc(
+ "wanted-list-initiator-label",
+ ("initiator", record.Initiator ?? Loc.GetString("wanted-list-unknown-initiator-label"))
+ ));
+
+ // History table
+ // Clear table if it exists
+ HistoryTable.RemoveAllChildren();
+
+ HistoryTable.AddChild(new Label()
+ {
+ Text = Loc.GetString("wanted-list-history-table-time-col"),
+ StyleClasses = { "LabelSmall" },
+ HorizontalAlignment = HAlignment.Center,
+ });
+ HistoryTable.AddChild(new Label()
+ {
+ Text = Loc.GetString("wanted-list-history-table-reason-col"),
+ StyleClasses = { "LabelSmall" },
+ HorizontalAlignment = HAlignment.Center,
+ HorizontalExpand = true,
+ });
+
+ HistoryTable.AddChild(new Label()
+ {
+ Text = Loc.GetString("wanted-list-history-table-initiator-col"),
+ StyleClasses = { "LabelSmall" },
+ HorizontalAlignment = HAlignment.Center,
+ });
+
+ if (record.History.Count > 0)
+ {
+ HistoryTable.Visible = true;
+
+ foreach (var history in record.History.OrderByDescending(h => h.AddTime))
+ {
+ HistoryTable.AddChild(new Label()
+ {
+ Text = $"{history.AddTime.Hours:00}:{history.AddTime.Minutes:00}:{history.AddTime.Seconds:00}",
+ StyleClasses = { "LabelSmall" },
+ VerticalAlignment = VAlignment.Top,
+ });
+
+ HistoryTable.AddChild(new RichTextLabel()
+ {
+ Text = $"[color=white]{history.Crime}[/color]",
+ HorizontalExpand = true,
+ VerticalAlignment = VAlignment.Top,
+ StyleClasses = { "LabelSubText" },
+ Margin = new(10f, 0f),
+ });
+
+ HistoryTable.AddChild(new RichTextLabel()
+ {
+ Text = $"[color=white]{history.InitiatorName}[/color]",
+ StyleClasses = { "LabelSubText" },
+ VerticalAlignment = VAlignment.Top,
+ });
+ }
+ }
+
+ RecordUnselected.Visible = false;
+ PersonContainer.Visible = true;
+
+ // Save selected item
+ _selectedTargetName = record.TargetInfo.Name;
+ }
+
+ private void GenerateItem(ListData data, ListContainerButton button)
+ {
+ if (data is not StatusListData(var record))
+ return;
+
+ var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal, HorizontalExpand = true };
+ var label = new Label() { Text = record.TargetInfo.Name };
+ var rect = new TextureRect()
+ {
+ TextureScale = new(2.2f),
+ VerticalAlignment = VAlignment.Center,
+ HorizontalAlignment = HAlignment.Center,
+ Margin = new(0f, 0f, 6f, 0f),
+ };
+
+ if (record.Status is not SecurityStatus.None)
+ {
+ var proto = "SecurityIcon" + record.Status switch
+ {
+ SecurityStatus.Detained => "Incarcerated",
+ _ => record.Status.ToString(),
+ };
+
+ if (_prototypeManager.TryIndex(proto, out var prototype))
+ {
+ rect.Texture = _spriteSystem.Frame0(prototype.Icon);
+ }
+ }
+
+ box.AddChild(rect);
+ box.AddChild(label);
+ button.AddChild(box);
+ button.AddStyleClass(ListContainer.StyleClassListContainerButton);
+
+ if (record.TargetInfo.Name.Equals(_selectedTargetName))
+ {
+ button.Pressed = true;
+ // For some reason the event is not called when `Pressed` changed, call it manually.
+ OnItemSelected(
+ new(button, new(new(), BoundKeyState.Down, new(), false, new(), new())),
+ data);
+ }
+ }
+}
+
+internal record StatusListData(WantedRecord Record) : ListData;
diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml
new file mode 100644
index 00000000000..7b5d116ad74
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Changelog/ChangelogTab.xaml.cs b/Content.Client/Changelog/ChangelogTab.xaml.cs
index 8fbeaab5f40..ca86f8a6b2e 100644
--- a/Content.Client/Changelog/ChangelogTab.xaml.cs
+++ b/Content.Client/Changelog/ChangelogTab.xaml.cs
@@ -131,13 +131,13 @@ public void PopulateChangelog(ChangelogManager.Changelog changelog)
Margin = new Thickness(6, 0, 0, 0),
};
authorLabel.SetMessage(
- FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author))));
+ FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", author))));
ChangelogBody.AddChild(authorLabel);
foreach (var change in groupedEntry.SelectMany(c => c.Changes))
{
var text = new RichTextLabel();
- text.SetMessage(FormattedMessage.FromMarkup(change.Message));
+ text.SetMessage(FormattedMessage.FromMarkupOrThrow(change.Message));
ChangelogBody.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml.cs b/Content.Client/Chat/UI/EmotesMenu.xaml.cs
index 33407553438..f3b7837f21a 100644
--- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs
+++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs
@@ -19,9 +19,6 @@ public sealed partial class EmotesMenu : RadialMenu
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
- private readonly SpriteSystem _spriteSystem;
- private readonly EntityWhitelistSystem _whitelistSystem;
-
public event Action>? OnPlayEmote;
public EmotesMenu()
@@ -29,8 +26,8 @@ public EmotesMenu()
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
- _spriteSystem = _entManager.System();
- _whitelistSystem = _entManager.System();
+ var spriteSystem = _entManager.System();
+ var whitelistSystem = _entManager.System();
var main = FindControl("Main");
@@ -40,8 +37,8 @@ public EmotesMenu()
var player = _playerManager.LocalSession?.AttachedEntity;
if (emote.Category == EmoteCategory.Invalid ||
emote.ChatTriggers.Count == 0 ||
- !(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
- _whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
+ !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
+ whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue;
if (!emote.Available &&
@@ -63,7 +60,7 @@ public EmotesMenu()
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
- Texture = _spriteSystem.Frame0(emote.Icon),
+ Texture = spriteSystem.Frame0(emote.Icon),
TextureScale = new Vector2(2f, 2f),
};
diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index adb61d10e62..32e9f4ae9be 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -180,7 +180,7 @@ protected FormattedMessage FormatSpeech(string message, Color? fontColor = null)
var msg = new FormattedMessage();
if (fontColor != null)
msg.PushColor(fontColor.Value);
- msg.AddMarkup(message);
+ msg.AddMarkupOrThrow(message);
return msg;
}
diff --git a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
index a3cedb5f2f3..7c7d824ee98 100644
--- a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
+++ b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
@@ -1,5 +1,5 @@
using System.Linq;
-using Content.Client.Chemistry.Containers.EntitySystems;
+using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Atmos.Prototypes;
using Content.Shared.Body.Part;
using Content.Shared.Chemistry;
@@ -16,7 +16,7 @@ namespace Content.Client.Chemistry.EntitySystems;
///
public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
{
- [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[ValidatePrototypeId]
private const string DefaultMixingCategory = "DummyMix";
diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs
index 96bbcc54f2a..27d77eda496 100644
--- a/Content.Client/Clothing/ClientClothingSystem.cs
+++ b/Content.Client/Clothing/ClientClothingSystem.cs
@@ -50,7 +50,6 @@ public sealed class ClientClothingSystem : ClothingSystem
};
[Dependency] private readonly IResourceCache _cache = default!;
- [Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
diff --git a/Content.Client/Commands/ActionsCommands.cs b/Content.Client/Commands/ActionsCommands.cs
index 593b8e82569..c155c7a9dea 100644
--- a/Content.Client/Commands/ActionsCommands.cs
+++ b/Content.Client/Commands/ActionsCommands.cs
@@ -1,6 +1,4 @@
using Content.Client.Actions;
-using Content.Client.Actions;
-using Content.Client.Mapping;
using Content.Shared.Administration;
using Robust.Shared.Console;
diff --git a/Content.Client/Commands/MappingClientSideSetupCommand.cs b/Content.Client/Commands/MappingClientSideSetupCommand.cs
index 3255e85e18f..d17f1fccaf8 100644
--- a/Content.Client/Commands/MappingClientSideSetupCommand.cs
+++ b/Content.Client/Commands/MappingClientSideSetupCommand.cs
@@ -13,7 +13,6 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
- [Dependency] private readonly IStateManager _stateManager = default!;
public override string Command => "mappingclientsidesetup";
diff --git a/Content.Client/Commands/SetMenuVisibilityCommand.cs b/Content.Client/Commands/SetMenuVisibilityCommand.cs
index ddfb0b16920..17a544dabaf 100644
--- a/Content.Client/Commands/SetMenuVisibilityCommand.cs
+++ b/Content.Client/Commands/SetMenuVisibilityCommand.cs
@@ -1,4 +1,5 @@
using Content.Client.Verbs;
+using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Console;
diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
index ea2f77d457d..83dc42c4a44 100644
--- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
+++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
@@ -4,14 +4,14 @@
MinSize="400 225">
-
-
+
+
-
+
-
+
diff --git a/Content.Client/Computer/ComputerBoundUserInterface.cs b/Content.Client/Computer/ComputerBoundUserInterface.cs
index 11c26b252e9..9f34eeda20f 100644
--- a/Content.Client/Computer/ComputerBoundUserInterface.cs
+++ b/Content.Client/Computer/ComputerBoundUserInterface.cs
@@ -11,8 +11,6 @@ namespace Content.Client.Computer
[Virtual]
public class ComputerBoundUserInterface : ComputerBoundUserInterfaceBase where TWindow : BaseWindow, IComputerWindow, new() where TState : BoundUserInterfaceState
{
- [Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
-
[ViewVariables]
private TWindow? _window;
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
index 269694ebf9e..81410dc7e6d 100644
--- a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Client.Materials;
+using Content.Client.Materials.UI;
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Content.Shared.Construction.Components;
diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 956f2fd0351..43fd8df2e19 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -23,9 +23,6 @@
-
-
-
diff --git a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
index 0462c095ba8..bda831394d3 100644
--- a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
+++ b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
@@ -9,6 +9,7 @@
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
+using Content.Shared.Verbs;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -194,8 +195,20 @@ public override void FrameUpdate(FrameEventArgs args)
return;
// Do we need to do in-range unOccluded checks?
- var ignoreFov = !_eyeManager.CurrentEye.DrawFov ||
- (_verbSystem.Visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov;
+ var visibility = _verbSystem.Visibility;
+
+ if (!_eyeManager.CurrentEye.DrawFov)
+ {
+ visibility &= ~MenuVisibility.NoFov;
+ }
+
+ var ev = new MenuVisibilityEvent()
+ {
+ Visibility = visibility,
+ };
+
+ _entityManager.EventBus.RaiseLocalEvent(player, ref ev);
+ visibility = ev.Visibility;
_entityManager.TryGetComponent(player, out ExaminerComponent? examiner);
var xformQuery = _entityManager.GetEntityQuery();
@@ -209,7 +222,7 @@ public override void FrameUpdate(FrameEventArgs args)
continue;
}
- if (ignoreFov)
+ if ((visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov)
continue;
var pos = new MapCoordinates(_xform.GetWorldPosition(xform, xformQuery), xform.MapID);
diff --git a/Content.Client/Crayon/CrayonSystem.cs b/Content.Client/Crayon/CrayonSystem.cs
index 2ed82b6a711..d489361afba 100644
--- a/Content.Client/Crayon/CrayonSystem.cs
+++ b/Content.Client/Crayon/CrayonSystem.cs
@@ -65,6 +65,17 @@ protected override void FrameUpdate(FrameEventArgs args)
}
_parent.UIUpdateNeeded = false;
+
+ // Frontier: unlimited crayon
+ if (_parent.Capacity == int.MaxValue)
+ {
+ _label.SetMarkup(Robust.Shared.Localization.Loc.GetString("crayon-drawing-label-unlimited",
+ ("color", _parent.Color),
+ ("state", _parent.SelectedState)));
+ return;
+ }
+ // End Frontier
+
_label.SetMarkup(Robust.Shared.Localization.Loc.GetString("crayon-drawing-label",
("color",_parent.Color),
("state",_parent.SelectedState),
diff --git a/Content.Client/Credits/CreditsWindow.xaml.cs b/Content.Client/Credits/CreditsWindow.xaml.cs
index 60ac5798454..ba240209533 100644
--- a/Content.Client/Credits/CreditsWindow.xaml.cs
+++ b/Content.Client/Credits/CreditsWindow.xaml.cs
@@ -145,7 +145,7 @@ void AddSection(string title, string path, bool markup = false)
var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
if (markup)
{
- label.SetMessage(FormattedMessage.FromMarkup(text.Trim()));
+ label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
}
else
{
diff --git a/Content.Client/CrewManifest/CrewManifestUi.xaml.cs b/Content.Client/CrewManifest/CrewManifestUi.xaml.cs
index 4183c908141..f07e54eb65b 100644
--- a/Content.Client/CrewManifest/CrewManifestUi.xaml.cs
+++ b/Content.Client/CrewManifest/CrewManifestUi.xaml.cs
@@ -1,3 +1,4 @@
+using Content.Client.CrewManifest.UI;
using Content.Shared.CrewManifest;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
index 21aa54c9622..7cae290fe17 100644
--- a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
+++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
@@ -227,7 +227,7 @@ private void PopulateRecordContainer(GeneralStationRecord stationRecord, Crimina
StatusOptionButton.SelectId((int) criminalRecord.Status);
if (criminalRecord.Reason is {} reason)
{
- var message = FormattedMessage.FromMarkup(Loc.GetString("criminal-records-console-wanted-reason"));
+ var message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-wanted-reason"));
message.AddText($": {reason}");
WantedReason.SetMessage(message);
WantedReason.Visible = true;
diff --git a/Content.Client/Decals/UI/PaletteColorPicker.xaml.cs b/Content.Client/Decals/UI/PaletteColorPicker.xaml.cs
index 2d6a89101c7..33f59e39676 100644
--- a/Content.Client/Decals/UI/PaletteColorPicker.xaml.cs
+++ b/Content.Client/Decals/UI/PaletteColorPicker.xaml.cs
@@ -26,6 +26,8 @@ public PaletteColorPicker()
var i = 0;
foreach (var palette in _prototypeManager.EnumeratePrototypes())
{
+ if (palette.Hidden) // Frontier: selectively hide palettes
+ continue; // Frontier: selectively hide palettes
Palettes.AddItem(palette.Name);
Palettes.SetItemMetadata(i, palette); // ew
i += 1;
diff --git a/Content.Client/DeltaV/Abilities/CrawlUnderObjectsSystem.cs b/Content.Client/DeltaV/Abilities/CrawlUnderObjectsSystem.cs
new file mode 100644
index 00000000000..879a5efee55
--- /dev/null
+++ b/Content.Client/DeltaV/Abilities/CrawlUnderObjectsSystem.cs
@@ -0,0 +1,44 @@
+using Content.Shared.DeltaV.Abilities;
+using Content.Shared.Popups;
+using Robust.Client.GameObjects;
+using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
+
+namespace Content.Client.DeltaV.Abilities;
+
+public sealed partial class HideUnderTableAbilitySystem : SharedCrawlUnderObjectsSystem
+{
+ [Dependency] private readonly AppearanceSystem _appearance = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAppearanceChange);
+ }
+
+ private void OnAppearanceChange(EntityUid uid,
+ CrawlUnderObjectsComponent component,
+ AppearanceChangeEvent args)
+ {
+ if (!TryComp(uid, out var sprite))
+ return;
+
+ _appearance.TryGetData(uid, SneakMode.Enabled, out bool enabled);
+ if (enabled)
+ {
+ if (component.OriginalDrawDepth != null)
+ return;
+
+ component.OriginalDrawDepth = sprite.DrawDepth;
+ sprite.DrawDepth = (int) DrawDepth.SmallMobs;
+ }
+ else
+ {
+ if (component.OriginalDrawDepth == null)
+ return;
+
+ sprite.DrawDepth = (int) component.OriginalDrawDepth;
+ component.OriginalDrawDepth = null;
+ }
+ }
+}
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml
index 4cd59f38b23..e832b4ee9f8 100644
--- a/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml
+++ b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml
@@ -1,6 +1,7 @@
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ Title="{Loc door-electronics-configuration-title}">
diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs
index 5a8838072c6..56d1a04c4ef 100644
--- a/Content.Client/Entry/EntryPoint.cs
+++ b/Content.Client/Entry/EntryPoint.cs
@@ -35,6 +35,7 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Replays;
using Robust.Shared.Timing;
+using Content.Client._NF.Emp.Overlays; // Frontier
namespace Content.Client.Entry
{
@@ -108,6 +109,7 @@ public override void Init()
_prototypeManager.RegisterIgnore("lobbyBackground");
_prototypeManager.RegisterIgnore("gamePreset");
_prototypeManager.RegisterIgnore("noiseChannel");
+ _prototypeManager.RegisterIgnore("playerConnectionWhitelist");
_prototypeManager.RegisterIgnore("spaceBiome");
_prototypeManager.RegisterIgnore("worldgenConfig");
_prototypeManager.RegisterIgnore("gcQueue");
@@ -153,6 +155,7 @@ public override void PostInit()
_overlayManager.AddOverlay(new SingularityOverlay());
_overlayManager.AddOverlay(new RadiationPulseOverlay());
+ _overlayManager.AddOverlay(new EmpBlastOverlay()); // Frontier
_chatManager.Initialize();
_clientPreferencesManager.Initialize();
_euiManager.Initialize();
diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs
index a941f0acff9..1c1f1984de4 100644
--- a/Content.Client/Examine/ExamineSystem.cs
+++ b/Content.Client/Examine/ExamineSystem.cs
@@ -1,7 +1,12 @@
+using System.Linq;
+using System.Numerics;
+using System.Threading;
using Content.Client.Verbs;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Item;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -12,13 +17,8 @@
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Utility;
-using System.Linq;
-using System.Numerics;
-using System.Threading;
using static Content.Shared.Interaction.SharedInteractionSystem;
using static Robust.Client.UserInterface.Controls.BoxContainer;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Item;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Examine
@@ -35,7 +35,6 @@ public sealed class ExamineSystem : ExamineSystemShared
private EntityUid _examinedEntity;
private EntityUid _lastExaminedEntity;
- private EntityUid _playerEntity;
private Popup? _examineTooltipOpen;
private ScreenCoordinates _popupPos;
private CancellationTokenSource? _requestCancelTokenSource;
@@ -74,9 +73,9 @@ private void OnExaminedItemDropped(EntityUid item, ItemComponent comp, DroppedEv
public override void Update(float frameTime)
{
if (_examineTooltipOpen is not {Visible: true}) return;
- if (!_examinedEntity.Valid || !_playerEntity.Valid) return;
+ if (!_examinedEntity.Valid || _playerManager.LocalEntity is not { } player) return;
- if (!CanExamine(_playerEntity, _examinedEntity))
+ if (!CanExamine(player, _examinedEntity))
CloseTooltip();
}
@@ -114,9 +113,8 @@ private bool HandleExamine(in PointerInputCmdHandler.PointerInputCmdArgs args)
return false;
}
- _playerEntity = _playerManager.LocalEntity ?? default;
-
- if (_playerEntity == default || !CanExamine(_playerEntity, entity))
+ if (_playerManager.LocalEntity is not { } player ||
+ !CanExamine(player, entity))
{
return false;
}
diff --git a/Content.Client/Explosion/ExplosionSystem.cs b/Content.Client/Explosion/ExplosionSystem.cs
index a2ed2d50e0d..692782ded4b 100644
--- a/Content.Client/Explosion/ExplosionSystem.cs
+++ b/Content.Client/Explosion/ExplosionSystem.cs
@@ -2,7 +2,4 @@
namespace Content.Client.Explosion.EntitySystems;
-public sealed class ExplosionSystem : SharedExplosionSystem
-{
-
-}
+public sealed class ExplosionSystem : SharedExplosionSystem;
diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs
index 046be2aa621..8e41c382dd7 100644
--- a/Content.Client/Flash/FlashOverlay.cs
+++ b/Content.Client/Flash/FlashOverlay.cs
@@ -16,6 +16,7 @@ public sealed class FlashOverlay : Overlay
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
+ private readonly SharedFlashSystem _flash;
private readonly StatusEffectsSystem _statusSys;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
@@ -27,6 +28,7 @@ public FlashOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index("FlashedEffect").InstanceUnique();
+ _flash = _entityManager.System();
_statusSys = _entityManager.System();
}
@@ -41,7 +43,7 @@ protected override void FrameUpdate(FrameEventArgs args)
|| !_entityManager.TryGetComponent(playerEntity, out var status))
return;
- if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status))
+ if (!_statusSys.TryGetTime(playerEntity.Value, _flash.FlashedKey, out var time, status))
return;
var curTime = _timing.CurTime;
diff --git a/Content.Client/GPS/Components/HandheldGPSComponent.cs b/Content.Client/GPS/Components/HandheldGPSComponent.cs
deleted file mode 100644
index 0f5271fd80c..00000000000
--- a/Content.Client/GPS/Components/HandheldGPSComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.GPS;
-
-namespace Content.Client.GPS.Components
-{
- [RegisterComponent]
- public sealed partial class HandheldGPSComponent : SharedHandheldGPSComponent
- {
- }
-}
diff --git a/Content.Client/GPS/Systems/HandheldGpsSystem.cs b/Content.Client/GPS/Systems/HandheldGpsSystem.cs
index cc2b888da37..3f38a3b6952 100644
--- a/Content.Client/GPS/Systems/HandheldGpsSystem.cs
+++ b/Content.Client/GPS/Systems/HandheldGpsSystem.cs
@@ -1,4 +1,4 @@
-using Content.Client.GPS.Components;
+using Content.Shared.GPS.Components;
using Content.Client.GPS.UI;
using Content.Client.Items;
diff --git a/Content.Client/GPS/UI/HandheldGpsStatusControl.cs b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
index 7dcf3f29c51..57645e386e7 100644
--- a/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
+++ b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
@@ -1,4 +1,4 @@
-using Content.Client.GPS.Components;
+using Content.Shared.GPS.Components;
using Content.Client.Message;
using Content.Client.Stylesheets;
using Robust.Client.GameObjects;
@@ -30,6 +30,13 @@ protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
+ // don't display the label if the gps component is being removed
+ if (_parent.Comp.LifeStage > ComponentLifeStage.Running)
+ {
+ _label.Visible = false;
+ return;
+ }
+
_updateDif += args.DeltaSeconds;
if (_updateDif < _parent.Comp.UpdateRate)
return;
@@ -44,9 +51,9 @@ private void UpdateGpsDetails()
var posText = "Error";
if (_entMan.TryGetComponent(_parent, out TransformComponent? transComp))
{
- var pos = _transform.GetMapCoordinates(_parent.Owner, xform: transComp);
- var x = (int) pos.X;
- var y = (int) pos.Y;
+ var pos = _transform.GetMapCoordinates(_parent.Owner, xform: transComp);
+ var x = (int)pos.X;
+ var y = (int)pos.Y;
posText = $"({x}, {y})";
}
_label.SetMarkup(Loc.GetString("handheld-gps-coordinates-title", ("coordinates", posText)));
diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
index fcf5ae91a49..d82724e6c7b 100644
--- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs
+++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
@@ -1,15 +1,14 @@
+using System.Linq;
using Content.Client.Administration.Managers;
using Content.Client.Gameplay;
using Content.Client.Lobby;
using Content.Client.RoundEnd;
using Content.Shared.GameTicking;
using Content.Shared.GameWindow;
-using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Client.UserInterface;
-using Robust.Shared.Prototypes;
namespace Content.Client.GameTicking.Managers
{
@@ -21,8 +20,7 @@ public sealed class ClientGameTicker : SharedGameTicker
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- private Dictionary, int?>> _jobsAvailable = new();
- private Dictionary _stationNames = new();
+ private Dictionary _stationJobInformationList = new();
[ViewVariables] public bool AreWeReady { get; private set; }
[ViewVariables] public bool IsGameStarted { get; private set; }
@@ -33,13 +31,21 @@ public sealed class ClientGameTicker : SharedGameTicker
[ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public new bool Paused { get; private set; }
- [ViewVariables] public IReadOnlyDictionary, int?>> JobsAvailable => _jobsAvailable;
- [ViewVariables] public IReadOnlyDictionary StationNames => _stationNames;
+ [ViewVariables] public IReadOnlyDictionary StationJobInformationList => _stationJobInformationList;
+
+ // Frontier addition
+ // Replaced StationNames with a getter that uses _stationJobInformationList
+ [ViewVariables]
+ public IReadOnlyDictionary StationNames =>
+ _stationJobInformationList.ToDictionary(
+ kvp => kvp.Key,
+ kvp => kvp.Value.StationName
+ );
public event Action? InfoBlobUpdated;
public event Action? LobbyStatusUpdated;
public event Action? LobbyLateJoinStatusUpdated;
- public event Action, int?>>>? LobbyJobsAvailableUpdated;
+ public event Action>? LobbyJobsAvailableUpdated;
public override void Initialize()
{
@@ -87,20 +93,9 @@ private void LateJoinStatus(TickerLateJoinStatusEvent message)
private void UpdateJobsAvailable(TickerJobsAvailableEvent message)
{
- _jobsAvailable.Clear();
-
- foreach (var (job, data) in message.JobsAvailableByStation)
- {
- _jobsAvailable[job] = data;
- }
-
- _stationNames.Clear();
- foreach (var weh in message.StationNames)
- {
- _stationNames[weh.Key] = weh.Value;
- }
-
- LobbyJobsAvailableUpdated?.Invoke(JobsAvailable);
+ _stationJobInformationList.Clear();
+ _stationJobInformationList = message.StationJobList;
+ LobbyJobsAvailableUpdated?.Invoke(StationJobInformationList);
}
private void JoinLobby(TickerJoinLobbyEvent message)
diff --git a/Content.Client/Ghost/GhostSystem.cs b/Content.Client/Ghost/GhostSystem.cs
index 181dbc2c9f6..6f91a0b3f60 100644
--- a/Content.Client/Ghost/GhostSystem.cs
+++ b/Content.Client/Ghost/GhostSystem.cs
@@ -120,8 +120,8 @@ private void OnToggleGhosts(EntityUid uid, GhostComponent component, ToggleGhost
if (args.Handled)
return;
- Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-ghost-visibility-popup"), args.Performer);
-
+ var locId = GhostVisibility ? "ghost-gui-toggle-ghost-visibility-popup-off" : "ghost-gui-toggle-ghost-visibility-popup-on";
+ Popup.PopupEntity(Loc.GetString(locId), args.Performer);
if (uid == _playerManager.LocalEntity)
ToggleGhostVisibility();
diff --git a/Content.Client/Gravity/GravitySystem.cs b/Content.Client/Gravity/GravitySystem.cs
index 3e87f76ba2b..dd51436a1f5 100644
--- a/Content.Client/Gravity/GravitySystem.cs
+++ b/Content.Client/Gravity/GravitySystem.cs
@@ -1,4 +1,5 @@
using Content.Shared.Gravity;
+using Content.Shared.Power;
using Robust.Client.GameObjects;
namespace Content.Client.Gravity;
@@ -21,7 +22,7 @@ private void OnAppearanceChange(EntityUid uid, SharedGravityGeneratorComponent c
if (args.Sprite == null)
return;
- if (_appearanceSystem.TryGetData(uid, GravityGeneratorVisuals.State, out var state, args.Component))
+ if (_appearanceSystem.TryGetData(uid, PowerChargeVisuals.State, out var state, args.Component))
{
if (comp.SpriteMap.TryGetValue(state, out var spriteState))
{
@@ -30,7 +31,7 @@ private void OnAppearanceChange(EntityUid uid, SharedGravityGeneratorComponent c
}
}
- if (_appearanceSystem.TryGetData(uid, GravityGeneratorVisuals.Charge, out var charge, args.Component))
+ if (_appearanceSystem.TryGetData(uid, PowerChargeVisuals.Charge, out var charge, args.Component))
{
var layer = args.Sprite.LayerMapGet(GravityGeneratorVisualLayers.Core);
switch (charge)
diff --git a/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs b/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs
deleted file mode 100644
index 32b40747d55..00000000000
--- a/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Content.Shared.Gravity;
-using JetBrains.Annotations;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.Gravity.UI
-{
- [UsedImplicitly]
- public sealed class GravityGeneratorBoundUserInterface : BoundUserInterface
- {
- [ViewVariables]
- private GravityGeneratorWindow? _window;
-
- public GravityGeneratorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
- {
- }
-
- protected override void Open()
- {
- base.Open();
-
- _window = this.CreateWindow();
- _window.SetEntity(Owner);
- }
-
- protected override void UpdateState(BoundUserInterfaceState state)
- {
- base.UpdateState(state);
-
- var castState = (SharedGravityGeneratorComponent.GeneratorState) state;
- _window?.UpdateState(castState);
- }
-
- public void SetPowerSwitch(bool on)
- {
- SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(on));
- }
- }
-}
diff --git a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml b/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml
deleted file mode 100644
index 853f437a2bf..00000000000
--- a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs b/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs
deleted file mode 100644
index 6f04133b594..00000000000
--- a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using Content.Shared.Gravity;
-using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
-
-namespace Content.Client.Gravity.UI
-{
- [GenerateTypedNameReferences]
- public sealed partial class GravityGeneratorWindow : FancyWindow
- {
- private readonly ButtonGroup _buttonGroup = new();
-
- public event Action? OnPowerSwitch;
-
- public GravityGeneratorWindow()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- OnButton.Group = _buttonGroup;
- OffButton.Group = _buttonGroup;
-
- OnButton.OnPressed += _ => OnPowerSwitch?.Invoke(true);
- OffButton.OnPressed += _ => OnPowerSwitch?.Invoke(false);
- }
-
- public void SetEntity(EntityUid uid)
- {
- EntityView.SetEntity(uid);
- }
-
- public void UpdateState(SharedGravityGeneratorComponent.GeneratorState state)
- {
- if (state.On)
- OnButton.Pressed = true;
- else
- OffButton.Pressed = true;
-
- PowerLabel.Text = Loc.GetString(
- "gravity-generator-window-power-label",
- ("draw", state.PowerDraw),
- ("max", state.PowerDrawMax));
-
- PowerLabel.SetOnlyStyleClass(MathHelper.CloseTo(state.PowerDraw, state.PowerDrawMax) ? "Good" : "Caution");
-
- ChargeBar.Value = state.Charge;
- ChargeText.Text = (state.Charge / 255f).ToString("P0");
- StatusLabel.Text = Loc.GetString(state.PowerStatus switch
- {
- GravityGeneratorPowerStatus.Off => "gravity-generator-window-status-off",
- GravityGeneratorPowerStatus.Discharging => "gravity-generator-window-status-discharging",
- GravityGeneratorPowerStatus.Charging => "gravity-generator-window-status-charging",
- GravityGeneratorPowerStatus.FullyCharged => "gravity-generator-window-status-fully-charged",
- _ => throw new ArgumentOutOfRangeException()
- });
-
- StatusLabel.SetOnlyStyleClass(state.PowerStatus switch
- {
- GravityGeneratorPowerStatus.Off => "Danger",
- GravityGeneratorPowerStatus.Discharging => "Caution",
- GravityGeneratorPowerStatus.Charging => "Caution",
- GravityGeneratorPowerStatus.FullyCharged => "Good",
- _ => throw new ArgumentOutOfRangeException()
- });
-
- EtaLabel.Text = state.EtaSeconds >= 0
- ? Loc.GetString("gravity-generator-window-eta-value", ("left", TimeSpan.FromSeconds(state.EtaSeconds)))
- : Loc.GetString("gravity-generator-window-eta-none");
-
- EtaLabel.SetOnlyStyleClass(state.EtaSeconds >= 0 ? "Caution" : "Disabled");
- }
- }
-}
diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
index 87931bf8455..f8d1c7e9720 100644
--- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
@@ -140,7 +140,7 @@ private void GenerateControl(ReagentPrototype reagent)
var i = 0;
foreach (var effectString in effect.EffectDescriptions)
{
- descMsg.AddMarkup(effectString);
+ descMsg.AddMarkupOrThrow(effectString);
i++;
if (i < descriptionsCount)
descMsg.PushNewline();
@@ -174,7 +174,7 @@ private void GenerateControl(ReagentPrototype reagent)
var i = 0;
foreach (var effectString in guideEntryRegistryPlant.PlantMetabolisms)
{
- descMsg.AddMarkup(effectString);
+ descMsg.AddMarkupOrThrow(effectString);
i++;
if (i < descriptionsCount)
descMsg.PushNewline();
@@ -195,7 +195,7 @@ private void GenerateControl(ReagentPrototype reagent)
FormattedMessage description = new();
description.AddText(reagent.LocalizedDescription);
description.PushNewline();
- description.AddMarkup(Loc.GetString("guidebook-reagent-physical-description",
+ description.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-physical-description",
("description", reagent.LocalizedPhysicalDescription)));
ReagentDescription.SetMessage(description);
}
diff --git a/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs
index 168f352d1ab..135dc5522ac 100644
--- a/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs
@@ -155,7 +155,7 @@ private void SetReagents(Dictionary reagents, ref RichTextL
var i = 0;
foreach (var (product, amount) in reagents.OrderByDescending(p => p.Value))
{
- msg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
+ msg.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-recipes-reagent-display",
("reagent", protoMan.Index(product).LocalizedName), ("ratio", amount)));
i++;
if (i < reagentCount)
diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
index 05c54700b82..82484690ba5 100644
--- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
@@ -23,6 +23,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
private readonly ISawmill _sawmill;
+ public ProtoId LastEntry;
+
public GuidebookWindow()
{
RobustXamlLoader.Load(this);
@@ -90,6 +92,8 @@ private void ShowGuide(GuideEntry entry)
_sawmill.Error($"Failed to parse contents of guide document {entry.Id}.");
}
+
+ LastEntry = entry.Id;
}
public void UpdateGuides(
diff --git a/Content.Client/Guidebook/DocumentParsingManager.cs b/Content.Client/Guidebook/DocumentParsingManager.cs
index 857ae552024..6201ddef365 100644
--- a/Content.Client/Guidebook/DocumentParsingManager.cs
+++ b/Content.Client/Guidebook/DocumentParsingManager.cs
@@ -36,7 +36,7 @@ public void Initialize()
.Assert(_tagControlParsers.ContainsKey, tag => $"unknown tag: {tag}")
.Bind(tag => _tagControlParsers[tag]);
- _controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser)
+ _controlParser = OneOf(_tagParser, TryHeaderControl, TryListControl, TextControlParser) // Frontier: ListControlParser())
diff --git a/Content.Client/Guidebook/DocumentParsingManager.static.cs b/Content.Client/Guidebook/DocumentParsingManager.static.cs
index 5d25d8f6452..87f5f30bac5 100644
--- a/Content.Client/Guidebook/DocumentParsingManager.static.cs
+++ b/Content.Client/Guidebook/DocumentParsingManager.static.cs
@@ -14,6 +14,7 @@ namespace Content.Client.Guidebook;
public sealed partial class DocumentParsingManager
{
private const string ListBullet = " › ";
+ private const string SublistBullet = " • "; // Frontier
// 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('\\')
@@ -123,6 +124,22 @@ public sealed partial class DocumentParsingManager
.Cast())
.Labelled("list");
+ // Frontier: sublists - should duplicate ListControlParser but for more hyphens, and print out more spaces before your list character
+ private static readonly Parser SublistControlParser = Try(String("--"))
+ .Then(SkipWhitespaces)
+ .Then(Map(
+ control => new BoxContainer
+ {
+ Children = { new Label { Text = SublistBullet, VerticalAlignment = VAlignment.Top }, control },
+ Orientation = LayoutOrientation.Horizontal
+ },
+ TextControlParser)
+ .Cast())
+ .Labelled("sublist");
+
+ private static readonly Parser TryListControl = OneOf(SublistControlParser, ListControlParser);
+ // End Frontier: sublists
+
#region Text Parsing
#region Basic Text Parsing
diff --git a/Content.Client/Guidebook/GuidebookDataSystem.cs b/Content.Client/Guidebook/GuidebookDataSystem.cs
new file mode 100644
index 00000000000..f47ad6ef1bb
--- /dev/null
+++ b/Content.Client/Guidebook/GuidebookDataSystem.cs
@@ -0,0 +1,45 @@
+using Content.Shared.Guidebook;
+
+namespace Content.Client.Guidebook;
+
+///
+/// Client system for storing and retrieving values extracted from entity prototypes
+/// for display in the guidebook ().
+/// Requests data from the server on .
+/// Can also be pushed new data when the server reloads prototypes.
+///
+public sealed class GuidebookDataSystem : EntitySystem
+{
+ private GuidebookData? _data;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeNetworkEvent(OnServerUpdated);
+
+ // Request data from the server
+ RaiseNetworkEvent(new RequestGuidebookDataEvent());
+ }
+
+ private void OnServerUpdated(UpdateGuidebookDataEvent args)
+ {
+ // Got new data from the server, either in response to our request, or because prototypes reloaded on the server
+ _data = args.Data;
+ _data.Freeze();
+ }
+
+ ///
+ /// Attempts to retrieve a value using the given identifiers.
+ /// See for more information.
+ ///
+ public bool TryGetValue(string prototype, string component, string field, out object? value)
+ {
+ if (_data == null)
+ {
+ value = null;
+ return false;
+ }
+ return _data.TryGetValue(prototype, component, field, out value);
+ }
+}
diff --git a/Content.Client/Guidebook/Richtext/ProtodataTag.cs b/Content.Client/Guidebook/Richtext/ProtodataTag.cs
new file mode 100644
index 00000000000..a725fd4e4b5
--- /dev/null
+++ b/Content.Client/Guidebook/Richtext/ProtodataTag.cs
@@ -0,0 +1,49 @@
+using System.Globalization;
+using Robust.Client.UserInterface.RichText;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Guidebook.RichText;
+
+///
+/// RichText tag that can display values extracted from entity prototypes.
+/// In order to be accessed by this tag, the desired field/property must
+/// be tagged with .
+///
+public sealed class ProtodataTag : IMarkupTag
+{
+ [Dependency] private readonly ILogManager _logMan = default!;
+ [Dependency] private readonly IEntityManager _entMan = default!;
+
+ public string Name => "protodata";
+ private ISawmill Log => _log ??= _logMan.GetSawmill("protodata_tag");
+ private ISawmill? _log;
+
+ public string TextBefore(MarkupNode node)
+ {
+ // Do nothing with an empty tag
+ if (!node.Value.TryGetString(out var prototype))
+ return string.Empty;
+
+ if (!node.Attributes.TryGetValue("comp", out var component))
+ return string.Empty;
+ if (!node.Attributes.TryGetValue("member", out var member))
+ return string.Empty;
+ node.Attributes.TryGetValue("format", out var format);
+
+ var guidebookData = _entMan.System();
+
+ // Try to get the value
+ if (!guidebookData.TryGetValue(prototype, component.StringValue!, member.StringValue!, out var value))
+ {
+ Log.Error($"Failed to find protodata for {component}.{member} in {prototype}");
+ return "???";
+ }
+
+ // If we have a format string and a formattable value, format it as requested
+ if (!string.IsNullOrEmpty(format.StringValue) && value is IFormattable formattable)
+ return formattable.ToString(format.StringValue, CultureInfo.CurrentCulture);
+
+ // No format string given, so just use default ToString
+ return value?.ToString() ?? "NULL";
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs
index 38760f4aa3c..baea03c8923 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs
@@ -17,6 +17,7 @@ public HealthAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owne
protected override void Open()
{
base.Open();
+
_window = this.CreateWindow();
_window.Title = EntMan.GetComponent(Owner).EntityName;
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
index 401f9768629..19d00a0bbf8 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
@@ -1,48 +1,65 @@
-
+ MaxHeight="525"
+ MinWidth="300">
+
-
-
-
+ Margin="0 0 0 5"
+ Orientation="Vertical">
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
index fcf6d4551fd..d61267d002c 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
@@ -1,12 +1,20 @@
using System.Linq;
using System.Numerics;
+using Content.Client.Message;
using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
+using Content.Shared.Alert;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
+using Content.Shared.Inventory;
using Content.Shared.MedicalScanner;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
@@ -28,9 +36,6 @@ public sealed partial class HealthAnalyzerWindow : FancyWindow
private readonly IPrototypeManager _prototypes;
private readonly IResourceCache _cache;
- private const int AnalyzerHeight = 430;
- private const int AnalyzerWidth = 300;
-
public HealthAnalyzerWindow()
{
RobustXamlLoader.Load(this);
@@ -44,8 +49,6 @@ public HealthAnalyzerWindow()
public void Populate(HealthAnalyzerScannedUserMessage msg)
{
- GroupsContainer.RemoveAllChildren();
-
var target = _entityManager.GetEntity(msg.TargetEntity);
if (target == null
@@ -57,82 +60,98 @@ public void Populate(HealthAnalyzerScannedUserMessage msg)
NoPatientDataText.Visible = false;
- string entityName = Loc.GetString("health-analyzer-window-entity-unknown-text");
- if (_entityManager.HasComponent(target.Value))
- {
- entityName = Identity.Name(target.Value, _entityManager);
- }
+ // Scan Mode
- if (msg.ScanMode.HasValue)
- {
- ScanModePanel.Visible = true;
- ScanModeText.Text = Loc.GetString(msg.ScanMode.Value ? "health-analyzer-window-scan-mode-active" : "health-analyzer-window-scan-mode-inactive");
- ScanModeText.FontColorOverride = msg.ScanMode.Value ? Color.Green : Color.Red;
- }
- else
- {
- ScanModePanel.Visible = false;
- }
+ ScanModeLabel.Text = msg.ScanMode.HasValue
+ ? msg.ScanMode.Value
+ ? Loc.GetString("health-analyzer-window-scan-mode-active")
+ : Loc.GetString("health-analyzer-window-scan-mode-inactive")
+ : Loc.GetString("health-analyzer-window-entity-unknown-text");
+
+ ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
+
+ // Patient Information
+
+ SpriteView.SetEntity(target.Value);
+ SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
+ NoDataTex.Visible = !SpriteView.Visible;
+
+ var name = new FormattedMessage();
+ name.PushColor(Color.White);
+ name.AddText(_entityManager.HasComponent(target.Value)
+ ? Identity.Name(target.Value, _entityManager)
+ : Loc.GetString("health-analyzer-window-entity-unknown-text"));
+ NameLabel.SetMessage(name);
+
+ SpeciesLabel.Text =
+ _entityManager.TryGetComponent(target.Value,
+ out var humanoidAppearanceComponent)
+ ? Loc.GetString(_prototypes.Index(humanoidAppearanceComponent.Species).Name)
+ : Loc.GetString("health-analyzer-window-entity-unknown-species-text");
+
+ // Basic Diagnostic
+
+ TemperatureLabel.Text = !float.IsNaN(msg.Temperature)
+ ? $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)"
+ : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
+
+ BloodLabel.Text = !float.IsNaN(msg.BloodLevel)
+ ? $"{msg.BloodLevel * 100:F1} %"
+ : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
- PatientName.Text = Loc.GetString(
- "health-analyzer-window-entity-health-text",
- ("entityName", entityName)
- );
+ StatusLabel.Text =
+ _entityManager.TryGetComponent(target.Value, out var mobStateComponent)
+ ? GetStatus(mobStateComponent.CurrentState)
+ : Loc.GetString("health-analyzer-window-entity-unknown-text");
- Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
- ("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)")
- );
+ // Total Damage
- BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",
- ("bloodLevel", float.IsNaN(msg.BloodLevel) ? "N/A" : $"{msg.BloodLevel * 100:F1} %")
- );
+ DamageLabel.Text = damageable.TotalDamage.ToString();
+
+ // Alerts
+
+ AlertsDivider.Visible = msg.Bleeding == true;
+ AlertsContainer.Visible = msg.Bleeding == true;
if (msg.Bleeding == true)
{
- Bleeding.Text = Loc.GetString("health-analyzer-window-entity-bleeding-text");
- Bleeding.FontColorOverride = Color.Red;
- }
- else
- {
- Bleeding.Text = string.Empty; // Clear the text
+ AlertsContainer.DisposeAllChildren();
+ AlertsContainer.AddChild(new Label
+ {
+ Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
+ FontColorOverride = Color.Red,
+ });
}
- patientDamageAmount.Text = Loc.GetString(
- "health-analyzer-window-entity-damage-total-text",
- ("amount", damageable.TotalDamage)
- );
+ // Damage Groups
var damageSortedGroups =
- damageable.DamagePerGroup.OrderBy(damage => damage.Value)
+ damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
.ToDictionary(x => x.Key, x => x.Value);
+
IReadOnlyDictionary damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
+ }
- if (_entityManager.TryGetComponent(target, out HungerComponent? hunger)
- && hunger.StarvationDamage != null
- && hunger.CurrentThreshold <= HungerThreshold.Starving)
+ private static string GetStatus(MobState mobState)
+ {
+ return mobState switch
{
- var box = new Control { Margin = new Thickness(0, 0, 0, 15) };
-
- box.AddChild(CreateDiagnosticGroupTitle(
- Loc.GetString("health-analyzer-window-malnutrition"),
- "malnutrition"));
-
- GroupsContainer.AddChild(box);
- }
-
- SetHeight = AnalyzerHeight;
- SetWidth = AnalyzerWidth;
+ MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
+ MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
+ MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
+ _ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
+ };
}
private void DrawDiagnosticGroups(
- Dictionary groups, IReadOnlyDictionary damageDict)
+ Dictionary groups,
+ IReadOnlyDictionary damageDict)
{
- HashSet shownTypes = new();
+ GroupsContainer.RemoveAllChildren();
- // Show the total damage and type breakdown for each damage group.
- foreach (var (damageGroupId, damageAmount) in groups.Reverse())
+ foreach (var (damageGroupId, damageAmount) in groups)
{
if (damageAmount == 0)
continue;
@@ -145,7 +164,6 @@ private void DrawDiagnosticGroups(
var groupContainer = new BoxContainer
{
- Margin = new Thickness(0, 0, 0, 15),
Align = BoxContainer.AlignMode.Begin,
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
@@ -159,23 +177,16 @@ private void DrawDiagnosticGroups(
foreach (var type in group.DamageTypes)
{
- if (damageDict.TryGetValue(type, out var typeAmount) && typeAmount > 0)
- {
- // If damage types are allowed to belong to more than one damage group,
- // they may appear twice here. Mark them as duplicate.
- if (shownTypes.Contains(type))
- continue;
-
- shownTypes.Add(type);
-
- var damageString = Loc.GetString(
- "health-analyzer-window-damage-type-text",
- ("damageType", _prototypes.Index(type).LocalizedName),
- ("amount", typeAmount)
- );
-
- groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, "- ")));
- }
+ if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
+ continue;
+
+ var damageString = Loc.GetString(
+ "health-analyzer-window-damage-type-text",
+ ("damageType", _prototypes.Index(type).LocalizedName),
+ ("amount", typeAmount)
+ );
+
+ groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
}
}
}
@@ -198,7 +209,6 @@ private static Label CreateDiagnosticItemLabel(string text)
{
return new Label
{
- Margin = new Thickness(2, 2),
Text = text,
};
}
@@ -207,13 +217,13 @@ private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
{
var rootContainer = new BoxContainer
{
+ Margin = new Thickness(0, 6, 0, 0),
VerticalAlignment = VAlignment.Bottom,
- Orientation = BoxContainer.LayoutOrientation.Horizontal
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
};
rootContainer.AddChild(new TextureRect
{
- Margin = new Thickness(0, 3),
SetSize = new Vector2(30, 30),
Texture = GetTexture(id.ToLower())
});
diff --git a/Content.Client/Info/InfoSection.xaml.cs b/Content.Client/Info/InfoSection.xaml.cs
index ab9d352d32f..9e10a4d7b4b 100644
--- a/Content.Client/Info/InfoSection.xaml.cs
+++ b/Content.Client/Info/InfoSection.xaml.cs
@@ -1,4 +1,4 @@
-using Robust.Client.AutoGenerated;
+using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
@@ -18,7 +18,7 @@ public void SetText(string title, string text, bool markup = false)
{
TitleLabel.Text = title;
if (markup)
- Content.SetMessage(FormattedMessage.FromMarkup(text.Trim()));
+ Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
else
Content.SetMessage(text);
}
diff --git a/Content.Client/Info/ServerInfo.cs b/Content.Client/Info/ServerInfo.cs
index 23be7506267..901fc913374 100644
--- a/Content.Client/Info/ServerInfo.cs
+++ b/Content.Client/Info/ServerInfo.cs
@@ -24,7 +24,7 @@ public ServerInfo()
}
public void SetInfoBlob(string markup)
{
- _richTextLabel.SetMessage(FormattedMessage.FromMarkup(markup));
+ _richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
}
}
}
diff --git a/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs b/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs
index 643ac47054b..0fa82d230d8 100644
--- a/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs
+++ b/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs
@@ -21,8 +21,24 @@ public sealed class MicrowaveBoundUserInterface : BoundUserInterface
[ViewVariables]
private readonly Dictionary _reagents = new();
+ // Frontier: UI parameters
+ private readonly string _menuTitle;
+ private readonly string _leftFlavorText;
+
public MicrowaveBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
+ // Frontier: switch UI params based on key
+ if ((MicrowaveUiKey)uiKey == MicrowaveUiKey.ElectricRangeKey)
+ {
+ _menuTitle = "microwave-menu-range-title";
+ _leftFlavorText = "microwave-menu-range-footer-flavor-left";
+ }
+ else
+ {
+ _menuTitle = "microwave-menu-title";
+ _leftFlavorText = "microwave-menu-footer-flavor-left";
+ }
+ // End Frontier
}
protected override void Open()
@@ -60,6 +76,11 @@ protected override void Open()
("time", Loc.GetString("microwave-menu-instant-button")));
}
};
+
+ // Frontier: UI customization
+ _menu.Title = Loc.GetString(_menuTitle);
+ _menu.LeftFooter.Text = Loc.GetString(_leftFlavorText);
+ // End Frontier
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Kitchen/UI/MicrowaveMenu.xaml b/Content.Client/Kitchen/UI/MicrowaveMenu.xaml
index cd81ea0a9bf..c99462d9e7a 100644
--- a/Content.Client/Kitchen/UI/MicrowaveMenu.xaml
+++ b/Content.Client/Kitchen/UI/MicrowaveMenu.xaml
@@ -98,7 +98,7 @@
-
+
diff --git a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
index 6b656123412..b9b58f23220 100644
--- a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
+++ b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
@@ -26,6 +26,11 @@ protected override void Open()
_window = this.CreateWindow();
+ if (_entManager.TryGetComponent(Owner, out HandLabelerComponent? labeler))
+ {
+ _window.SetMaxLabelLength(labeler!.MaxLabelChars);
+ }
+
_window.OnLabelChanged += OnLabelChanged;
Reload();
}
diff --git a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
index 6482cdc1cc2..7a0627b3e23 100644
--- a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
+++ b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
@@ -21,7 +21,7 @@ public HandLabelerWindow()
{
RobustXamlLoader.Load(this);
- LabelLineEdit.OnTextEntered += e =>
+ LabelLineEdit.OnTextChanged += e =>
{
_label = e.Text;
OnLabelChanged?.Invoke(_label);
@@ -33,6 +33,10 @@ public HandLabelerWindow()
_focused = false;
LabelLineEdit.Text = _label;
};
+
+ // Give the editor keybard focus, since that's the only
+ // thing the user will want to be doing with this UI
+ LabelLineEdit.GrabKeyboardFocus();
}
public void SetCurrentLabel(string label)
@@ -44,5 +48,10 @@ public void SetCurrentLabel(string label)
if (!_focused)
LabelLineEdit.Text = label;
}
+
+ public void SetMaxLabelLength(int maxLength)
+ {
+ LabelLineEdit.IsValid = s => s.Length <= maxLength;
+ }
}
}
diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs
index 9e523364c29..e33f433052b 100644
--- a/Content.Client/LateJoin/LateJoinGui.cs
+++ b/Content.Client/LateJoin/LateJoinGui.cs
@@ -6,6 +6,7 @@
using Content.Client.UserInterface.Controls;
using Content.Client.Players.PlayTimeTracking;
using Content.Shared.CCVar;
+using Content.Shared.GameTicking;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
@@ -171,12 +172,12 @@ private void RebuildUI()
{
var departmentName = Loc.GetString($"department-{department.ID}");
_jobCategories[id] = new Dictionary();
- var stationAvailable = _gameTicker.JobsAvailable[id];
+ var stationAvailable = _gameTicker.StationJobInformationList[id];
var jobsAvailable = new List();
foreach (var jobId in department.Roles)
{
- if (!stationAvailable.ContainsKey(jobId))
+ if (!stationAvailable.JobsAvailable.ContainsKey(jobId))
continue;
jobsAvailable.Add(_prototypeManager.Index(jobId));
@@ -225,7 +226,8 @@ private void RebuildUI()
foreach (var prototype in jobsAvailable)
{
- var value = stationAvailable[prototype.ID];
+ // Frontier: stationAvailable[prototype.ID]; -> stationAvailable.JobsAvailable[prototype.ID];
+ var value = stationAvailable.JobsAvailable[prototype.ID];
var jobLabel = new Label
{
@@ -292,29 +294,25 @@ private void RebuildUI()
}
}
- private void JobsAvailableUpdated(IReadOnlyDictionary, int?>> updatedJobs)
+ private void JobsAvailableUpdated(IReadOnlyDictionary updatedJobs)
{
- foreach (var stationEntries in updatedJobs)
+ // Frontier: Made this more readable with simplified comparisons and LINQ expressions.
+ // Feel free to replace this with upstream code whenever, just mind that
+ // updatedJobs is now a dictionary of NetEntity to StationJobInformation.
+ // I changed this: jobInformation.TryGetValue to this: jobInformation.JobsAvailable.TryGetValue
+ // Godspeed.
+ foreach (var (stationNetEntity, jobInformation) in updatedJobs)
{
- if (_jobButtons.ContainsKey(stationEntries.Key))
+ if (!_jobButtons.TryGetValue(stationNetEntity, out var existingJobEntries))
+ continue;
+ foreach (var existingJobEntry in existingJobEntries)
{
- var jobsAvailable = stationEntries.Value;
-
- var existingJobEntries = _jobButtons[stationEntries.Key];
- foreach (var existingJobEntry in existingJobEntries)
+ if (!jobInformation.JobsAvailable.TryGetValue(existingJobEntry.Key, out var updatedJobValue))
+ continue;
+ foreach (var matchingJobButton in existingJobEntry.Value.Where(matchingJobButton => matchingJobButton.Amount != updatedJobValue))
{
- if (jobsAvailable.ContainsKey(existingJobEntry.Key))
- {
- var updatedJobValue = jobsAvailable[existingJobEntry.Key];
- foreach (var matchingJobButton in existingJobEntry.Value)
- {
- if (matchingJobButton.Amount != updatedJobValue)
- {
- matchingJobButton.RefreshLabel(updatedJobValue);
- matchingJobButton.Disabled |= matchingJobButton.Amount == 0;
- }
- }
- }
+ matchingJobButton.RefreshLabel(updatedJobValue);
+ matchingJobButton.Disabled |= matchingJobButton.Amount == 0;
}
}
}
diff --git a/Content.Client/Launcher/LauncherConnecting.cs b/Content.Client/Launcher/LauncherConnecting.cs
index 29b241ae5df..33d31cc52d8 100644
--- a/Content.Client/Launcher/LauncherConnecting.cs
+++ b/Content.Client/Launcher/LauncherConnecting.cs
@@ -19,6 +19,7 @@ public sealed class LauncherConnecting : Robust.Client.State.State
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly IClipboardManager _clipboard = default!;
private LauncherConnectingGui? _control;
@@ -58,7 +59,7 @@ private set
protected override void Startup()
{
- _control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg);
+ _control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _clipboard);
_userInterfaceManager.StateRoot.AddChild(_control);
diff --git a/Content.Client/Launcher/LauncherConnectingGui.xaml b/Content.Client/Launcher/LauncherConnectingGui.xaml
index 3734fa5b3ad..2cb349d4e77 100644
--- a/Content.Client/Launcher/LauncherConnectingGui.xaml
+++ b/Content.Client/Launcher/LauncherConnectingGui.xaml
@@ -18,21 +18,38 @@
-
-
+
-
+
+
+
+
-
+
-
+
+
+
+
diff --git a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
index 5015b710eb4..cf89f98095e 100644
--- a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
+++ b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
@@ -28,14 +28,16 @@ public sealed partial class LauncherConnectingGui : Control
private readonly IRobustRandom _random;
private readonly IPrototypeManager _prototype;
private readonly IConfigurationManager _cfg;
+ private readonly IClipboardManager _clipboard;
public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
- IPrototypeManager prototype, IConfigurationManager config)
+ IPrototypeManager prototype, IConfigurationManager config, IClipboardManager clipboard)
{
_state = state;
_random = random;
_prototype = prototype;
_cfg = config;
+ _clipboard = clipboard;
RobustXamlLoader.Load(this);
@@ -44,8 +46,11 @@ public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
Stylesheet = IoCManager.Resolve().SheetSpace;
ChangeLoginTip();
- ReconnectButton.OnPressed += ReconnectButtonPressed;
RetryButton.OnPressed += ReconnectButtonPressed;
+ ReconnectButton.OnPressed += ReconnectButtonPressed;
+
+ CopyButton.OnPressed += CopyButtonPressed;
+ CopyButtonDisconnected.OnPressed += CopyButtonDisconnectedPressed;
ExitButton.OnPressed += _ => _state.Exit();
var addr = state.Address;
@@ -78,6 +83,24 @@ private void ReconnectButtonPressed(BaseButton.ButtonEventArgs args)
_state.RetryConnect();
}
+ private void CopyButtonPressed(BaseButton.ButtonEventArgs args)
+ {
+ CopyText(ConnectFailReason.Text);
+ }
+
+ private void CopyButtonDisconnectedPressed(BaseButton.ButtonEventArgs args)
+ {
+ CopyText(DisconnectReason.Text);
+ }
+
+ private void CopyText(string? text)
+ {
+ if (!string.IsNullOrEmpty(text))
+ {
+ _clipboard.SetText(text);
+ }
+ }
+
private void ConnectFailReasonChanged(string? reason)
{
ConnectFailReason.SetMessage(reason == null
diff --git a/Content.Client/Light/Components/LightBehaviourComponent.cs b/Content.Client/Light/Components/LightBehaviourComponent.cs
index 9df793ee93c..246863ba60f 100644
--- a/Content.Client/Light/Components/LightBehaviourComponent.cs
+++ b/Content.Client/Light/Components/LightBehaviourComponent.cs
@@ -359,9 +359,6 @@ void ISerializationHooks.AfterDeserialization()
[RegisterComponent]
public sealed partial class LightBehaviourComponent : SharedLightBehaviourComponent, ISerializationHooks
{
- [Dependency] private readonly IEntityManager _entMan = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
-
public const string KeyPrefix = nameof(LightBehaviourComponent);
public sealed class AnimationContainer
diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs
index f9aaad27a8f..d03cc0a725a 100644
--- a/Content.Client/Lobby/LobbyState.cs
+++ b/Content.Client/Lobby/LobbyState.cs
@@ -1,8 +1,7 @@
-using Content.Client._NF.Latejoin;
-using Content.Client.Chat.Managers;
+using Content.Client._NF.LateJoin;
using Content.Client.Audio;
+using Content.Client.Eui;
using Content.Client.GameTicking.Managers;
-using Content.Client.LateJoin;
using Content.Client.Lobby.UI;
using Content.Client.Message;
using Content.Client.UserInterface.Systems.Chat;
@@ -13,6 +12,7 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
+using PickerWindow = Content.Client._NF.LateJoin.Windows.PickerWindow;
namespace Content.Client.Lobby
@@ -33,6 +33,9 @@ public sealed class LobbyState : Robust.Client.State.State
protected override Type? LinkedScreenType { get; } = typeof(LobbyGui);
public LobbyGui? Lobby;
+ // Frontier - save pickerwindow so it opens only once
+ private PickerWindow? _pickerWindow = null;
+
protected override void Startup()
{
if (_userInterfaceManager.ActiveScreen == null)
@@ -99,8 +102,10 @@ private void OnReadyPressed(BaseButton.ButtonEventArgs args)
{
return;
}
-
- new NFLateJoinGui().OpenCentered();
+ // Frontier to downstream: if you want to skip the first window and go straight to station picker,
+ // simply change the enum to station or crew in the PickerWindow constructor.
+ _pickerWindow ??= new PickerWindow();
+ _pickerWindow.OpenCentered();
}
private void OnReadyToggled(BaseButton.ButtonToggledEventArgs args)
diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
index 89e2a243814..23c97936669 100644
--- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
@@ -744,6 +744,9 @@ private void ReloadPreview()
PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed);
SpriteView.SetEntity(PreviewDummy);
_entManager.System().SetEntityName(PreviewDummy, Profile.Name);
+
+ // Check and set the dirty flag to enable the save/reset buttons as appropriate.
+ SetDirty();
}
///
@@ -804,6 +807,9 @@ private void ReloadProfilePreview()
return;
_entManager.System().LoadProfile(PreviewDummy, Profile);
+
+ // Check and set the dirty flag to enable the save/reset buttons as appropriate.
+ SetDirty();
}
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
@@ -1040,7 +1046,6 @@ private void OpenLoadout(JobPrototype? jobProto, RoleLoadout roleLoadout, RoleLo
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
Profile = Profile?.WithLoadout(roleLoadout);
- SetDirty();
ReloadPreview();
};
@@ -1049,7 +1054,6 @@ private void OpenLoadout(JobPrototype? jobProto, RoleLoadout roleLoadout, RoleLo
roleLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager);
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
Profile = Profile?.WithLoadout(roleLoadout);
- SetDirty();
ReloadPreview();
};
@@ -1059,7 +1063,6 @@ private void OpenLoadout(JobPrototype? jobProto, RoleLoadout roleLoadout, RoleLo
_loadoutWindow.OnClose += () =>
{
JobOverride = null;
- SetDirty();
ReloadPreview();
};
@@ -1084,7 +1087,6 @@ private void OnMarkingChange(MarkingSet markings)
return;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
- SetDirty();
ReloadProfilePreview();
}
@@ -1152,7 +1154,6 @@ private void OnSkinColorOnValueChanged()
}
}
- SetDirty();
ReloadProfilePreview();
}
@@ -1183,7 +1184,6 @@ private void SetAge(int newAge)
{
Profile = Profile?.WithAge(newAge);
ReloadPreview();
- SetDirty();
}
private void SetSex(Sex newSex)
@@ -1206,14 +1206,12 @@ private void SetSex(Sex newSex)
UpdateGenderControls();
Markings.SetSex(newSex);
ReloadPreview();
- SetDirty();
}
private void SetGender(Gender newGender)
{
Profile = Profile?.WithGender(newGender);
ReloadPreview();
- SetDirty();
}
private void SetSpecies(string newSpecies)
@@ -1229,7 +1227,6 @@ private void SetSpecies(string newSpecies)
RefreshTraits(); // Frontier
UpdateSexControls(); // update sex for new species
UpdateSpeciesGuidebookIcon();
- SetDirty();
ReloadPreview();
}
diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml
index b79ffdd3a0f..f9c9dee7eed 100644
--- a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml
@@ -1,14 +1,26 @@
-
-
-
-
-
+ MinSize="800 128">
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
index 30c92ff26eb..bd590957bd7 100644
--- a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
@@ -1,3 +1,4 @@
+using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared._NF.Bank;
using Content.Shared.Preferences;
@@ -7,6 +8,7 @@
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
namespace Content.Client.Lobby.UI.Loadouts;
@@ -26,26 +28,36 @@ public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, Role
Profile = profile;
var protoManager = collection.Resolve();
- foreach (var group in proto.Groups)
+ // Hide if no groups
+ if (proto.Groups.Count == 0)
{
- if (!protoManager.TryIndex(group, out var groupProto))
- continue;
+ LoadoutGroupsContainer.Visible = false;
+ SetSize = Vector2.Zero;
+ }
+ else
+ {
+ foreach (var group in proto.Groups)
+ {
+ if (!protoManager.TryIndex(group, out var groupProto))
+ continue;
- if (groupProto.Hidden)
- continue;
+ if (groupProto.Hidden)
+ continue;
- var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
- LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
- _groups.Add(container);
- container.OnLoadoutPressed += args =>
- {
- OnLoadoutPressed?.Invoke(group, args);
- };
+ var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
+ LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
+ _groups.Add(container);
- container.OnLoadoutUnpressed += args =>
- {
- OnLoadoutUnpressed?.Invoke(group, args);
- };
+ container.OnLoadoutPressed += args =>
+ {
+ OnLoadoutPressed?.Invoke(group, args);
+ };
+
+ container.OnLoadoutUnpressed += args =>
+ {
+ OnLoadoutUnpressed?.Invoke(group, args);
+ };
+ }
}
//Frontier - we inject our label here but it needs recalculating every time a new item is selected,
//so we add a new method and call it there too.
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
index a941addac17..2252b559905 100644
--- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
+++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
@@ -1,5 +1,6 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
+using Prometheus;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml b/Content.Client/Lobby/UI/LobbyGui.xaml
index 8283b0d5d6a..3efcdfbe1b2 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml
@@ -14,7 +14,7 @@
Stretch="KeepAspectCovered" />
-
+
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
index 6471edb6f37..81230130a1d 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
@@ -2,6 +2,7 @@
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
+using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
diff --git a/Content.Client/MachineLinking/UI/SignalTimerBoundUserInterface.cs b/Content.Client/MachineLinking/UI/SignalTimerBoundUserInterface.cs
index 11abe8c2451..0607c768315 100644
--- a/Content.Client/MachineLinking/UI/SignalTimerBoundUserInterface.cs
+++ b/Content.Client/MachineLinking/UI/SignalTimerBoundUserInterface.cs
@@ -7,8 +7,6 @@ namespace Content.Client.MachineLinking.UI;
public sealed class SignalTimerBoundUserInterface : BoundUserInterface
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
-
[ViewVariables]
private SignalTimerWindow? _window;
diff --git a/Content.Client/MachineLinking/UI/SignalTimerWindow.xaml.cs b/Content.Client/MachineLinking/UI/SignalTimerWindow.xaml.cs
index 6133abfcb70..441ca9ea365 100644
--- a/Content.Client/MachineLinking/UI/SignalTimerWindow.xaml.cs
+++ b/Content.Client/MachineLinking/UI/SignalTimerWindow.xaml.cs
@@ -3,6 +3,7 @@
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using Content.Client.TextScreen;
+using Robust.Client.UserInterface.Controls;
namespace Content.Client.MachineLinking.UI;
diff --git a/Content.Client/Mapping/MappingScreen.xaml b/Content.Client/Mapping/MappingScreen.xaml
index b6413608478..9cc3e734f0e 100644
--- a/Content.Client/Mapping/MappingScreen.xaml
+++ b/Content.Client/Mapping/MappingScreen.xaml
@@ -78,6 +78,7 @@
ToolTip="Pick (Hold 5)" />
+
diff --git a/Content.Client/Mapping/MappingScreen.xaml.cs b/Content.Client/Mapping/MappingScreen.xaml.cs
index b2ad2fd83fb..46c0e51fad6 100644
--- a/Content.Client/Mapping/MappingScreen.xaml.cs
+++ b/Content.Client/Mapping/MappingScreen.xaml.cs
@@ -96,6 +96,22 @@ public MappingScreen()
Pick.Texture.TexturePath = "/Textures/Interface/eyedropper.svg.png";
Delete.Texture.TexturePath = "/Textures/Interface/eraser.svg.png";
+ Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png";
+ Flip.OnPressed += args => FlipSides();
+ }
+
+ public void FlipSides()
+ {
+ ScreenContainer.Flip();
+
+ if (SpawnContainer.GetPositionInParent() == 0)
+ {
+ Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png";
+ }
+ else
+ {
+ Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png";
+ }
}
private void OnDecalColorPicked(Color color)
diff --git a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml
index 2b600845cae..f4fb9da0622 100644
--- a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml
+++ b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml
@@ -47,8 +47,10 @@
+
+ StyleClasses="OpenBoth" Text="{Loc news-write-ui-preview-text}"/>
diff --git a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs
index 5e068f1e9c5..90a66bec7f3 100644
--- a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs
+++ b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs
@@ -14,6 +14,7 @@ namespace Content.Client.MassMedia.Ui;
public sealed partial class ArticleEditorPanel : Control
{
public event Action? PublishButtonPressed;
+ public event Action? ArticleDraftUpdated;
private bool _preview;
@@ -45,6 +46,7 @@ public ArticleEditorPanel()
ButtonPreview.OnPressed += OnPreview;
ButtonCancel.OnPressed += OnCancel;
ButtonPublish.OnPressed += OnPublish;
+ ButtonSaveDraft.OnPressed += OnDraftSaved;
TitleField.OnTextChanged += args => OnTextChanged(args.Text.Length, args.Control, SharedNewsSystem.MaxTitleLength);
ContentField.OnTextChanged += args => OnTextChanged(Rope.CalcTotalLength(args.TextRope), args.Control, SharedNewsSystem.MaxContentLength);
@@ -68,6 +70,9 @@ private void OnTextChanged(long length, Control control, long maxLength)
ButtonPublish.Disabled = false;
ButtonPreview.Disabled = false;
}
+
+ // save draft regardless; they can edit down the length later
+ ArticleDraftUpdated?.Invoke(TitleField.Text, Rope.Collapse(ContentField.TextRope));
}
private void OnPreview(BaseButton.ButtonEventArgs eventArgs)
@@ -92,6 +97,12 @@ private void OnPublish(BaseButton.ButtonEventArgs eventArgs)
Visible = false;
}
+ private void OnDraftSaved(BaseButton.ButtonEventArgs eventArgs)
+ {
+ ArticleDraftUpdated?.Invoke(TitleField.Text, Rope.Collapse(ContentField.TextRope));
+ Visible = false;
+ }
+
private void Reset()
{
_preview = false;
@@ -100,6 +111,7 @@ private void Reset()
PreviewLabel.SetMarkup("");
TitleField.Text = "";
ContentField.TextRope = Rope.Leaf.Empty;
+ ArticleDraftUpdated?.Invoke(string.Empty, string.Empty);
}
protected override void Dispose(bool disposing)
diff --git a/Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs b/Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs
index 22e5bc452a0..4f21361990a 100644
--- a/Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs
+++ b/Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs
@@ -25,6 +25,9 @@ protected override void Open()
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;
_menu.DeleteButtonPressed += OnDeleteButtonPressed;
+ _menu.CreateButtonPressed += OnCreateButtonPressed;
+ _menu.ArticleEditorPanel.ArticleDraftUpdated += OnArticleDraftUpdated;
+
SendMessage(new NewsWriterArticlesRequestMessage());
}
@@ -34,7 +37,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
if (state is not NewsWriterBoundUserInterfaceState cast)
return;
- _menu?.UpdateUI(cast.Articles, cast.PublishEnabled, cast.NextPublish);
+ _menu?.UpdateUI(cast.Articles, cast.PublishEnabled, cast.NextPublish, cast.DraftTitle, cast.DraftContent);
}
private void OnPublishButtonPressed()
@@ -67,4 +70,14 @@ private void OnDeleteButtonPressed(int articleNum)
SendMessage(new NewsWriterDeleteMessage(articleNum));
}
+
+ private void OnCreateButtonPressed()
+ {
+ SendMessage(new NewsWriterRequestDraftMessage());
+ }
+
+ private void OnArticleDraftUpdated(string title, string content)
+ {
+ SendMessage(new NewsWriterSaveDraftMessage(title, content));
+ }
}
diff --git a/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs b/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs
index c059ce785af..af1f9a94414 100644
--- a/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs
+++ b/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs
@@ -4,6 +4,7 @@
using Content.Shared.MassMedia.Systems;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
+using Robust.Shared.Utility;
namespace Content.Client.MassMedia.Ui;
@@ -16,6 +17,8 @@ public sealed partial class NewsWriterMenu : FancyWindow
public event Action? DeleteButtonPressed;
+ public event Action? CreateButtonPressed;
+
public NewsWriterMenu()
{
RobustXamlLoader.Load(this);
@@ -31,7 +34,7 @@ public NewsWriterMenu()
ButtonCreate.OnPressed += OnCreate;
}
- public void UpdateUI(NewsArticle[] articles, bool publishEnabled, TimeSpan nextPublish)
+ public void UpdateUI(NewsArticle[] articles, bool publishEnabled, TimeSpan nextPublish, string draftTitle, string draftContent)
{
ArticlesContainer.Children.Clear();
ArticleCount.Text = Loc.GetString("news-write-ui-article-count-text", ("count", articles.Length));
@@ -54,6 +57,9 @@ public void UpdateUI(NewsArticle[] articles, bool publishEnabled, TimeSpan nextP
ButtonCreate.Disabled = !publishEnabled;
_nextPublish = nextPublish;
+
+ ArticleEditorPanel.TitleField.Text = draftTitle;
+ ArticleEditorPanel.ContentField.TextRope = new Rope.Leaf(draftContent);
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -93,5 +99,6 @@ protected override void Dispose(bool disposing)
private void OnCreate(BaseButton.ButtonEventArgs buttonEventArgs)
{
ArticleEditorPanel.Visible = true;
+ CreateButtonPressed?.Invoke();
}
}
diff --git a/Content.Client/Materials/RecyclerVisualizerSystem.cs b/Content.Client/Materials/RecyclerVisualizerSystem.cs
new file mode 100644
index 00000000000..646ae406aa0
--- /dev/null
+++ b/Content.Client/Materials/RecyclerVisualizerSystem.cs
@@ -0,0 +1,27 @@
+using Content.Shared.Conveyor;
+using Content.Shared.Materials;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Materials;
+
+public sealed class RecyclerVisualizerSystem : VisualizerSystem
+{
+ protected override void OnAppearanceChange(EntityUid uid, RecyclerVisualsComponent component, ref AppearanceChangeEvent args)
+ {
+ if (args.Sprite == null || !args.Sprite.LayerMapTryGet(RecyclerVisualLayers.Main, out var layer))
+ return;
+
+ AppearanceSystem.TryGetData(uid, ConveyorVisuals.State, out var running);
+ AppearanceSystem.TryGetData(uid, RecyclerVisuals.Bloody, out var bloody);
+ AppearanceSystem.TryGetData(uid, RecyclerVisuals.Broken, out var broken);
+
+ var activityState = running == ConveyorState.Off ? 0 : 1;
+ if (broken) //breakage overrides activity
+ activityState = 2;
+
+ var bloodyKey = bloody ? component.BloodyKey : string.Empty;
+
+ var state = $"{component.BaseKey}{activityState}{bloodyKey}";
+ args.Sprite.LayerSetState(layer, state);
+ }
+}
diff --git a/Content.Client/Materials/RecyclerVisualsComponent.cs b/Content.Client/Materials/RecyclerVisualsComponent.cs
new file mode 100644
index 00000000000..4db26326d6a
--- /dev/null
+++ b/Content.Client/Materials/RecyclerVisualsComponent.cs
@@ -0,0 +1,17 @@
+namespace Content.Client.Materials;
+
+[RegisterComponent]
+public sealed partial class RecyclerVisualsComponent : Component
+{
+ ///
+ /// Key appended to state string if bloody.
+ ///
+ [DataField]
+ public string BloodyKey = "bld";
+
+ ///
+ /// Base key for the visual state.
+ ///
+ [DataField]
+ public string BaseKey = "grinder-o";
+}
diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs
index 21b77d70e7a..6f87cc2783c 100644
--- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs
+++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs
@@ -1,13 +1,14 @@
using Content.Client.Pinpointer.UI;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.GameObjects; // Frontier modification
using Robust.Shared.Timing;
namespace Content.Client.Medical.CrewMonitoring;
public sealed partial class CrewMonitoringNavMapControl : NavMapControl
{
+ [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
+ private readonly SharedTransformSystem _transform = default!; // Frontier
public NetEntity? Focus;
public Dictionary LocalizedNames = new();
@@ -16,6 +17,8 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
public CrewMonitoringNavMapControl() : base()
{
+ IoCManager.InjectDependencies(this); // Frontier
+ _transform = _entitySystem.GetEntitySystem(); // Frontier
WallColor = new Color(192, 122, 196);
TileColor = new(71, 42, 72);
BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
@@ -67,7 +70,8 @@ protected override void FrameUpdate(FrameEventArgs args)
// Text location of the blip will display GPS coordinates for the purpose of being able to find a person via GPS
// Previously it displayed coordinates relative to the center of the station, which had no use.
- var message = name + "\nLocation: [x = " + MathF.Round(blip.MapCoordinates.X) + ", y = " + MathF.Round(blip.MapCoordinates.Y) + "]"; // Frontier modification
+ var mapCoords = _transform.ToMapCoordinates(blip.Coordinates); // Frontier
+ var message = name + "\nLocation: [x = " + MathF.Round(mapCoords.X) + ", y = " + MathF.Round(mapCoords.Y) + "]"; // Frontier: use map coords
_trackedEntityLabel.Text = message;
_trackedEntityPanel.Visible = true;
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 @@
+
+
sensors, EntityUid monitor, Entit
// Show monitor on nav map
if (monitorCoords != null && _blipTexture != null)
{
- NavMap.TrackedEntities[_entManager.GetNetEntity(monitor)] = new NavMapBlip(monitorCoords.Value, monitorCoords.Value.ToMap(_entManager, _transformSystem), _blipTexture, Color.Cyan, true, false); // Frontier modification
+ NavMap.TrackedEntities[_entManager.GetNetEntity(monitor)] = new NavMapBlip(monitorCoords.Value, _blipTexture, Color.Cyan, true, false); // Frontier modification
}
}
@@ -158,6 +158,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
@@ -291,7 +296,6 @@ private void PopulateDepartmentList(IEnumerable departmentSens
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
new NavMapBlip
(coordinates.Value,
- coordinates.Value.ToMap(_entManager, _transformSystem), // Frontier modification
_blipTexture,
(_trackedEntity == null || sensor.SuitSensorUid == _trackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
sensor.SuitSensorUid == _trackedEntity));
@@ -358,7 +362,6 @@ private void UpdateSensorsTable(NetEntity? currTrackedEntity, NetEntity? prevTra
{
data = new NavMapBlip
(data.Coordinates,
- data.Coordinates.ToMap(_entManager, _transformSystem), // Frontier modification
data.Texture,
(currTrackedEntity == null || castSensor.SuitSensorUid == currTrackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
castSensor.SuitSensorUid == currTrackedEntity);
diff --git a/Content.Client/Message/RichTextLabelExt.cs b/Content.Client/Message/RichTextLabelExt.cs
index 7ff6390764b..ee3c00fa1b8 100644
--- a/Content.Client/Message/RichTextLabelExt.cs
+++ b/Content.Client/Message/RichTextLabelExt.cs
@@ -15,7 +15,7 @@ public static class RichTextLabelExt
///
public static RichTextLabel SetMarkup(this RichTextLabel label, string markup)
{
- label.SetMessage(FormattedMessage.FromMarkup(markup));
+ label.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
return label;
}
diff --git a/Content.Client/Mining/MiningOverlay.cs b/Content.Client/Mining/MiningOverlay.cs
new file mode 100644
index 00000000000..b23835b36ee
--- /dev/null
+++ b/Content.Client/Mining/MiningOverlay.cs
@@ -0,0 +1,96 @@
+using System.Numerics;
+using Content.Shared.Mining.Components;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Mining;
+
+public sealed class MiningOverlay : Overlay
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ private readonly EntityLookupSystem _lookup;
+ private readonly SpriteSystem _sprite;
+ private readonly TransformSystem _xform;
+
+ private readonly EntityQuery _spriteQuery;
+ private readonly EntityQuery _xformQuery;
+
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+ public override bool RequestScreenTexture => false;
+
+ private readonly HashSet> _viewableEnts = new();
+
+ public MiningOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+
+ _lookup = _entityManager.System();
+ _sprite = _entityManager.System();
+ _xform = _entityManager.System();
+
+ _spriteQuery = _entityManager.GetEntityQuery();
+ _xformQuery = _entityManager.GetEntityQuery();
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ var handle = args.WorldHandle;
+
+ if (_player.LocalEntity is not { } localEntity ||
+ !_entityManager.TryGetComponent(localEntity, out var viewerComp))
+ return;
+
+ if (viewerComp.LastPingLocation == null)
+ return;
+
+ var scaleMatrix = Matrix3Helpers.CreateScale(Vector2.One);
+
+ _viewableEnts.Clear();
+ _lookup.GetEntitiesInRange(viewerComp.LastPingLocation.Value, viewerComp.ViewRange, _viewableEnts);
+ foreach (var ore in _viewableEnts)
+ {
+ if (!_xformQuery.TryComp(ore, out var xform) ||
+ !_spriteQuery.TryComp(ore, out var sprite))
+ continue;
+
+ if (xform.MapID != args.MapId || !sprite.Visible)
+ continue;
+
+ if (!sprite.LayerMapTryGet(MiningScannerVisualLayers.Overlay, out var idx))
+ continue;
+ var layer = sprite[idx];
+
+ if (layer.ActualRsi?.Path == null || layer.RsiState.Name == null)
+ continue;
+
+ var gridRot = xform.GridUid == null ? 0 : _xformQuery.CompOrNull(xform.GridUid.Value)?.LocalRotation ?? 0;
+ var rotationMatrix = Matrix3Helpers.CreateRotation(gridRot);
+
+ var worldMatrix = Matrix3Helpers.CreateTranslation(_xform.GetWorldPosition(xform));
+ var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
+ var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
+ handle.SetTransform(matty);
+
+ var spriteSpec = new SpriteSpecifier.Rsi(layer.ActualRsi.Path, layer.RsiState.Name);
+ var texture = _sprite.GetFrame(spriteSpec, TimeSpan.FromSeconds(layer.AnimationTime));
+
+ var animTime = (viewerComp.NextPingTime - _timing.CurTime).TotalSeconds;
+
+
+ var alpha = animTime < viewerComp.AnimationDuration
+ ? 0
+ : (float) Math.Clamp((animTime - viewerComp.AnimationDuration) / viewerComp.AnimationDuration, 0f, 1f);
+ var color = Color.White.WithAlpha(alpha);
+
+ handle.DrawTexture(texture, -(Vector2) texture.Size / 2f / EyeManager.PixelsPerMeter, layer.Rotation, modulate: color);
+
+ }
+ handle.SetTransform(Matrix3x2.Identity);
+ }
+}
diff --git a/Content.Client/Mining/MiningOverlaySystem.cs b/Content.Client/Mining/MiningOverlaySystem.cs
new file mode 100644
index 00000000000..294cab30ca8
--- /dev/null
+++ b/Content.Client/Mining/MiningOverlaySystem.cs
@@ -0,0 +1,54 @@
+using Content.Shared.Mining.Components;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Player;
+
+namespace Content.Client.Mining;
+
+///
+/// This handles the lifetime of the for a given entity.
+///
+public sealed class MiningOverlaySystem : EntitySystem
+{
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IOverlayManager _overlayMan = default!;
+
+ private MiningOverlay _overlay = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+
+ _overlay = new();
+ }
+
+ private void OnPlayerAttached(Entity ent, ref LocalPlayerAttachedEvent args)
+ {
+ _overlayMan.AddOverlay(_overlay);
+ }
+
+ private void OnPlayerDetached(Entity ent, ref LocalPlayerDetachedEvent args)
+ {
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+
+ private void OnInit(Entity ent, ref ComponentInit args)
+ {
+ if (_player.LocalEntity == ent)
+ {
+ _overlayMan.AddOverlay(_overlay);
+ }
+ }
+
+ private void OnShutdown(Entity ent, ref ComponentShutdown args)
+ {
+ if (_player.LocalEntity == ent)
+ {
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+ }
+}
diff --git a/Content.Client/Mining/OreVeinVisualsComponent.cs b/Content.Client/Mining/OreVeinVisualsComponent.cs
deleted file mode 100644
index c662111c3ed..00000000000
--- a/Content.Client/Mining/OreVeinVisualsComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Content.Client.Mining;
-
-public sealed class OreVeinVisualsComponent
-{
-
-}
diff --git a/Content.Client/Nuke/NukeMenu.xaml.cs b/Content.Client/Nuke/NukeMenu.xaml.cs
index b498d0e3bbc..aa757584733 100644
--- a/Content.Client/Nuke/NukeMenu.xaml.cs
+++ b/Content.Client/Nuke/NukeMenu.xaml.cs
@@ -107,7 +107,7 @@ public void UpdateState(NukeUiState state)
FirstStatusLabel.Text = firstMsg;
SecondStatusLabel.Text = secondMsg;
- EjectButton.Disabled = !state.DiskInserted || state.Status == NukeStatus.ARMED;
+ EjectButton.Disabled = !state.DiskInserted || state.Status == NukeStatus.ARMED || !state.IsAnchored;
AnchorButton.Disabled = state.Status == NukeStatus.ARMED;
AnchorButton.Pressed = state.IsAnchored;
ArmButton.Disabled = !state.AllowArm || !state.IsAnchored;
diff --git a/Content.Client/Nutrition/EntitySystems/ClientFoodSequenceSystem.cs b/Content.Client/Nutrition/EntitySystems/ClientFoodSequenceSystem.cs
index e571c5a856c..c708c6fe7d2 100644
--- a/Content.Client/Nutrition/EntitySystems/ClientFoodSequenceSystem.cs
+++ b/Content.Client/Nutrition/EntitySystems/ClientFoodSequenceSystem.cs
@@ -1,7 +1,6 @@
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Robust.Client.GameObjects;
-using Robust.Shared.Utility;
namespace Content.Client.Nutrition.EntitySystems;
@@ -50,6 +49,7 @@ private void UpdateFoodVisuals(Entity start, Sp
sprite.AddBlankLayer(index);
sprite.LayerMapSet(keyCode, index);
sprite.LayerSetSprite(index, state.Sprite);
+ sprite.LayerSetScale(index, state.Scale);
//Offset the layer
var layerPos = start.Comp.StartPosition;
diff --git a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs
index 689dcc00fcb..82beefe9eca 100644
--- a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs
+++ b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs
@@ -1,7 +1,6 @@
-using Content.Shared.Kitchen.Components;
using Content.Shared.Nyanotrasen.Kitchen.Components;
-namespace Content.Client.Kitchen.Components
+namespace Content.Client.Nyanotrasen.Kitchen.Components
{
[RegisterComponent]
//Unnecessary item: [ComponentReference(typeof(SharedDeepFriedComponent))]
diff --git a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs
index 97fea3d0ca9..c8a5b1fe375 100644
--- a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs
+++ b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs
@@ -4,14 +4,20 @@
using Content.Client.Kitchen.Components;
using Content.Shared.Clothing;
using Content.Shared.Hands;
-using Content.Shared.Kitchen.Components;
using Content.Shared.Nyanotrasen.Kitchen.Components;
+using Robust.Shared.Prototypes;
+using Content.Shared.Nyanotrasen.Kitchen.Prototypes;
+using Content.Client.Nyanotrasen.Kitchen.Components;
namespace Content.Client.Kitchen.Visualizers
{
public sealed class DeepFriedVisualizerSystem : VisualizerSystem
{
- private readonly static string ShaderName = "Crispy";
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ private const string FriedShader = "Crispy";
+ private const string SpectralShader = "Spectral";
public override void Initialize()
{
@@ -26,11 +32,14 @@ protected override void OnAppearanceChange(EntityUid uid, DeepFriedComponent com
if (args.Sprite == null)
return;
- if (!args.Component.TryGetData(DeepFriedVisuals.Fried, out bool isFried))
+ // Frontier: get shader to use
+ var shader = GetDeepFriedEntityShader(uid, args.Component);
+ if (shader == null)
return;
+ // End Frontier
for (var i = 0; i < args.Sprite.AllLayers.Count(); ++i)
- args.Sprite.LayerSetShader(i, ShaderName);
+ args.Sprite.LayerSetShader(i, shader); // Frontier: ShaderName
{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
protected override void OnAppearanceChange(EntityUid uid, DeepFryerComponent component, ref AppearanceChangeEvent args)
{
- if (!args.Component.TryGetData(DeepFryerVisuals.Bubbling, out bool isBubbling) ||
+ if (!_appearance.TryGetData(uid, DeepFryerVisuals.Bubbling, out bool isBubbling, args.Component) ||
!TryComp(uid, out var scvComponent))
{
return;
diff --git a/Content.Client/Nyanotrasen/Mail/MailSystem.cs b/Content.Client/Nyanotrasen/Mail/MailSystem.cs
index 13cf12061f7..3d622383c1c 100644
--- a/Content.Client/Nyanotrasen/Mail/MailSystem.cs
+++ b/Content.Client/Nyanotrasen/Mail/MailSystem.cs
@@ -31,15 +31,15 @@ public sealed class MailJobVisualizerSystem : VisualizerSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
protected override void OnAppearanceChange(EntityUid uid, MailComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
- args.Component.TryGetData(MailVisuals.JobIcon, out string job);
-
- if (!_prototypeManager.TryIndex(job, out var icon))
+ if (!_appearance.TryGetData(uid, MailVisuals.JobIcon, out string job) ||
+ !_prototypeManager.TryIndex(job, out var icon))
return;
args.Sprite.LayerSetTexture(MailVisualLayers.JobStamp, _spriteSystem.Frame0(icon.Icon));
diff --git a/Content.Client/Options/UI/OptionsMenu.xaml.cs b/Content.Client/Options/UI/OptionsMenu.xaml.cs
index 8ff7c2021e5..301bafba12c 100644
--- a/Content.Client/Options/UI/OptionsMenu.xaml.cs
+++ b/Content.Client/Options/UI/OptionsMenu.xaml.cs
@@ -1,3 +1,4 @@
+using Content.Client.Options.UI.Tabs;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
diff --git a/Content.Client/PDA/PdaBoundUserInterface.cs b/Content.Client/PDA/PdaBoundUserInterface.cs
index 6d16136503c..7fc23bf7bb9 100644
--- a/Content.Client/PDA/PdaBoundUserInterface.cs
+++ b/Content.Client/PDA/PdaBoundUserInterface.cs
@@ -4,18 +4,20 @@
using Content.Shared.PDA;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
-using Robust.Shared.Configuration;
namespace Content.Client.PDA
{
[UsedImplicitly]
public sealed class PdaBoundUserInterface : CartridgeLoaderBoundUserInterface
{
+ private readonly PdaSystem _pdaSystem;
+
[ViewVariables]
private PdaMenu? _menu;
public PdaBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
+ _pdaSystem = EntMan.System();
}
protected override void Open()
@@ -97,7 +99,13 @@ protected override void UpdateState(BoundUserInterfaceState state)
if (state is not PdaUpdateState updateState)
return;
- _menu?.UpdateState(updateState);
+ if (_menu == null)
+ {
+ _pdaSystem.Log.Error("PDA state received before menu was created.");
+ return;
+ }
+
+ _menu.UpdateState(updateState);
}
protected override void AttachCartridgeUI(Control cartridgeUIFragment, string? title)
diff --git a/Content.Client/PDA/PdaMenu.xaml b/Content.Client/PDA/PdaMenu.xaml
index c8bcee25f15..36accc99290 100644
--- a/Content.Client/PDA/PdaMenu.xaml
+++ b/Content.Client/PDA/PdaMenu.xaml
@@ -76,14 +76,17 @@
Description="{Loc 'comp-pda-ui-ringtone-button-description'}"/>
diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs
index 03df383eebc..c97110b208e 100644
--- a/Content.Client/Physics/Controllers/MoverController.cs
+++ b/Content.Client/Physics/Controllers/MoverController.cs
@@ -8,132 +8,131 @@
using Robust.Shared.Player;
using Robust.Shared.Timing;
-namespace Content.Client.Physics.Controllers
+namespace Content.Client.Physics.Controllers;
+
+public sealed class MoverController : SharedMoverController
{
- public sealed class MoverController : SharedMoverController
- {
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnRelayPlayerAttached);
- SubscribeLocalEvent(OnRelayPlayerDetached);
- SubscribeLocalEvent(OnPlayerAttached);
- SubscribeLocalEvent(OnPlayerDetached);
-
- SubscribeLocalEvent(OnUpdatePredicted);
- SubscribeLocalEvent(OnUpdateRelayTargetPredicted);
- SubscribeLocalEvent(OnUpdatePullablePredicted);
- }
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnRelayPlayerAttached);
+ SubscribeLocalEvent(OnRelayPlayerDetached);
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+
+ SubscribeLocalEvent(OnUpdatePredicted);
+ SubscribeLocalEvent(OnUpdateRelayTargetPredicted);
+ SubscribeLocalEvent(OnUpdatePullablePredicted);
+ }
- private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args)
- {
- // Enable prediction if an entity is controlled by the player
- if (entity.Owner == _playerManager.LocalEntity)
- args.IsPredicted = true;
- }
+ private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args)
+ {
+ // Enable prediction if an entity is controlled by the player
+ if (entity.Owner == _playerManager.LocalEntity)
+ args.IsPredicted = true;
+ }
- private void OnUpdateRelayTargetPredicted(Entity entity, ref UpdateIsPredictedEvent args)
- {
- if (entity.Comp.Source == _playerManager.LocalEntity)
- args.IsPredicted = true;
- }
+ private void OnUpdateRelayTargetPredicted(Entity entity, ref UpdateIsPredictedEvent args)
+ {
+ if (entity.Comp.Source == _playerManager.LocalEntity)
+ args.IsPredicted = true;
+ }
- private void OnUpdatePullablePredicted(Entity entity, ref UpdateIsPredictedEvent args)
- {
- // Enable prediction if an entity is being pulled by the player.
- // Disable prediction if an entity is being pulled by some non-player entity.
+ private void OnUpdatePullablePredicted(Entity entity, ref UpdateIsPredictedEvent args)
+ {
+ // Enable prediction if an entity is being pulled by the player.
+ // Disable prediction if an entity is being pulled by some non-player entity.
- if (entity.Comp.Puller == _playerManager.LocalEntity)
- args.IsPredicted = true;
- else if (entity.Comp.Puller != null)
- args.BlockPrediction = true;
+ if (entity.Comp.Puller == _playerManager.LocalEntity)
+ args.IsPredicted = true;
+ else if (entity.Comp.Puller != null)
+ args.BlockPrediction = true;
- // TODO recursive pulling checks?
- // What if the entity is being pulled by a vehicle controlled by the player?
- }
+ // TODO recursive pulling checks?
+ // What if the entity is being pulled by a vehicle controlled by the player?
+ }
- private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args)
- {
- Physics.UpdateIsPredicted(entity.Owner);
- Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
- if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
- SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
- }
+ private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args)
+ {
+ Physics.UpdateIsPredicted(entity.Owner);
+ Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
+ if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
+ SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
+ }
- private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args)
- {
- Physics.UpdateIsPredicted(entity.Owner);
- Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
- if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
- SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
- }
+ private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args)
+ {
+ Physics.UpdateIsPredicted(entity.Owner);
+ Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
+ if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
+ SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
+ }
- private void OnPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args)
- {
- SetMoveInput(entity, MoveButtons.None);
- }
+ private void OnPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args)
+ {
+ SetMoveInput(entity, MoveButtons.None);
+ }
- private void OnPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args)
- {
- SetMoveInput(entity, MoveButtons.None);
- }
+ private void OnPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args)
+ {
+ SetMoveInput(entity, MoveButtons.None);
+ }
- public override void UpdateBeforeSolve(bool prediction, float frameTime)
- {
- base.UpdateBeforeSolve(prediction, frameTime);
+ public override void UpdateBeforeSolve(bool prediction, float frameTime)
+ {
+ base.UpdateBeforeSolve(prediction, frameTime);
- if (_playerManager.LocalEntity is not {Valid: true} player)
- return;
+ if (_playerManager.LocalEntity is not {Valid: true} player)
+ return;
- if (RelayQuery.TryGetComponent(player, out var relayMover))
- HandleClientsideMovement(relayMover.RelayEntity, frameTime);
+ if (RelayQuery.TryGetComponent(player, out var relayMover))
+ HandleClientsideMovement(relayMover.RelayEntity, frameTime);
- HandleClientsideMovement(player, frameTime);
- }
+ HandleClientsideMovement(player, frameTime);
+ }
- private void HandleClientsideMovement(EntityUid player, float frameTime)
+ private void HandleClientsideMovement(EntityUid player, float frameTime)
+ {
+ if (!MoverQuery.TryGetComponent(player, out var mover) ||
+ !XformQuery.TryGetComponent(player, out var xform))
{
- if (!MoverQuery.TryGetComponent(player, out var mover) ||
- !XformQuery.TryGetComponent(player, out var xform))
- {
- return;
- }
-
- var physicsUid = player;
- PhysicsComponent? body;
- var xformMover = xform;
+ return;
+ }
- if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid))
- {
- if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) ||
- !XformQuery.TryGetComponent(xform.ParentUid, out xformMover))
- {
- return;
- }
+ var physicsUid = player;
+ PhysicsComponent? body;
+ var xformMover = xform;
- physicsUid = xform.ParentUid;
- }
- else if (!PhysicsQuery.TryGetComponent(player, out body))
+ if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid))
+ {
+ if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) ||
+ !XformQuery.TryGetComponent(xform.ParentUid, out xformMover))
{
return;
}
- // Server-side should just be handled on its own so we'll just do this shizznit
- HandleMobMovement(
- player,
- mover,
- physicsUid,
- body,
- xformMover,
- frameTime);
+ physicsUid = xform.ParentUid;
}
-
- protected override bool CanSound()
+ else if (!PhysicsQuery.TryGetComponent(player, out body))
{
- return _timing is { IsFirstTimePredicted: true, InSimulation: true };
+ return;
}
+
+ // Server-side should just be handled on its own so we'll just do this shizznit
+ HandleMobMovement(
+ player,
+ mover,
+ physicsUid,
+ body,
+ xformMover,
+ frameTime);
+ }
+
+ protected override bool CanSound()
+ {
+ return _timing is { IsFirstTimePredicted: true, InSimulation: true };
}
}
diff --git a/Content.Client/Physics/JointVisualsOverlay.cs b/Content.Client/Physics/JointVisualsOverlay.cs
index e0b3499a974..9cc2831d212 100644
--- a/Content.Client/Physics/JointVisualsOverlay.cs
+++ b/Content.Client/Physics/JointVisualsOverlay.cs
@@ -1,9 +1,8 @@
+using System.Numerics;
using Content.Shared.Physics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Dynamics.Joints;
namespace Content.Client.Physics;
@@ -16,8 +15,6 @@ public sealed class JointVisualsOverlay : Overlay
private IEntityManager _entManager;
- private HashSet _drawn = new();
-
public JointVisualsOverlay(IEntityManager entManager)
{
_entManager = entManager;
@@ -25,7 +22,6 @@ public JointVisualsOverlay(IEntityManager entManager)
protected override void Draw(in OverlayDrawArgs args)
{
- _drawn.Clear();
var worldHandle = args.WorldHandle;
var spriteSystem = _entManager.System();
@@ -33,12 +29,14 @@ protected override void Draw(in OverlayDrawArgs args)
var joints = _entManager.EntityQueryEnumerator();
var xformQuery = _entManager.GetEntityQuery();
+ args.DrawingHandle.SetTransform(Matrix3x2.Identity);
+
while (joints.MoveNext(out var visuals, out var xform))
{
if (xform.MapID != args.MapId)
continue;
- var other = visuals.Target;
+ var other = _entManager.GetEntity(visuals.Target);
if (!xformQuery.TryGetComponent(other, out var otherXform))
continue;
diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs
index 53169acd0e6..413b41c36a6 100644
--- a/Content.Client/Pinpointer/UI/NavMapControl.cs
+++ b/Content.Client/Pinpointer/UI/NavMapControl.cs
@@ -718,16 +718,14 @@ protected Vector2 GetOffset()
public struct NavMapBlip
{
public EntityCoordinates Coordinates;
- public MapCoordinates MapCoordinates; //Frontier modification
public Texture Texture;
public Color Color;
public bool Blinks;
public bool Selectable;
- public NavMapBlip(EntityCoordinates coordinates, MapCoordinates mapCoordinates, Texture texture, Color color, bool blinks, bool selectable = true) //Frontier modification
+ public NavMapBlip(EntityCoordinates coordinates, Texture texture, Color color, bool blinks, bool selectable = true)
{
Coordinates = coordinates;
- MapCoordinates = mapCoordinates; // Frontier modification
Texture = texture;
Color = color;
Blinks = blinks;
diff --git a/Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml b/Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml
new file mode 100644
index 00000000000..e1c55131cd6
--- /dev/null
+++ b/Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml.cs b/Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml.cs
new file mode 100644
index 00000000000..a4d4055c7df
--- /dev/null
+++ b/Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml.cs
@@ -0,0 +1,50 @@
+using Content.Shared.Pinpointer;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Map;
+
+namespace Content.Client.Pinpointer.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class StationMapBeaconControl : Control, IComparable
+{
+ [Dependency] private readonly IEntityManager _entMan = default!;
+
+ public readonly EntityCoordinates BeaconPosition;
+ public Action? OnPressed;
+ public string? Label => BeaconNameLabel.Text;
+ private StyleBoxFlat _styleBox;
+ public Color Color => _styleBox.BackgroundColor;
+
+ public StationMapBeaconControl(EntityUid mapUid, SharedNavMapSystem.NavMapBeacon beacon)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ BeaconPosition = new EntityCoordinates(mapUid, beacon.Position);
+
+ _styleBox = new StyleBoxFlat { BackgroundColor = beacon.Color };
+ ColorPanel.PanelOverride = _styleBox;
+ BeaconNameLabel.Text = beacon.Text;
+
+ MainButton.OnPressed += args => OnPressed?.Invoke(BeaconPosition);
+ }
+
+ public int CompareTo(StationMapBeaconControl? other)
+ {
+ if (other == null)
+ return 1;
+
+ // Group by color
+ var colorCompare = Color.ToArgb().CompareTo(other.Color.ToArgb());
+ if (colorCompare != 0)
+ {
+ return colorCompare;
+ }
+
+ // If same color, sort by text
+ return string.Compare(Label, other.Label);
+ }
+}
diff --git a/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs b/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
index 91fb4ef71bd..3d1eb1723c3 100644
--- a/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
+++ b/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
@@ -24,9 +24,16 @@ protected override void Open()
_window = this.CreateWindow();
_window.Title = EntMan.GetComponent(Owner).EntityName;
+
+ string stationName = string.Empty;
+ if(EntMan.TryGetComponent(gridUid, out var gridMetaData))
+ {
+ stationName = gridMetaData.EntityName;
+ }
+
if (EntMan.TryGetComponent(Owner, out var comp) && comp.ShowLocation)
- _window.Set(gridUid, Owner);
+ _window.Set(stationName, gridUid, Owner);
else
- _window.Set(gridUid, null);
+ _window.Set(stationName, gridUid, null);
}
}
diff --git a/Content.Client/Pinpointer/UI/StationMapWindow.xaml b/Content.Client/Pinpointer/UI/StationMapWindow.xaml
index 00424a3566a..c79fc8f9e7b 100644
--- a/Content.Client/Pinpointer/UI/StationMapWindow.xaml
+++ b/Content.Client/Pinpointer/UI/StationMapWindow.xaml
@@ -3,11 +3,28 @@
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
Title="{Loc 'station-map-window-title'}"
Resizable="False"
- SetSize="668 713"
- MinSize="668 713">
+ SetSize="868 748"
+ MinSize="868 748">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs b/Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs
index 7cbb8b7d0db..52ef2ab7da4 100644
--- a/Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs
+++ b/Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs
@@ -3,24 +3,75 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map;
+using Content.Shared.Pinpointer;
namespace Content.Client.Pinpointer.UI;
[GenerateTypedNameReferences]
public sealed partial class StationMapWindow : FancyWindow
{
+ [Dependency] private readonly IEntityManager _entMan = default!;
+
+ private readonly List _buttons = new();
+
public StationMapWindow()
{
RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ FilterBar.OnTextChanged += (bar) => OnFilterChanged(bar.Text);
}
- public void Set(EntityUid? mapUid, EntityUid? trackedEntity)
+ public void Set(string stationName, EntityUid? mapUid, EntityUid? trackedEntity)
{
NavMapScreen.MapUid = mapUid;
if (trackedEntity != null)
NavMapScreen.TrackedCoordinates.Add(new EntityCoordinates(trackedEntity.Value, Vector2.Zero), (true, Color.Cyan));
+ if (!string.IsNullOrEmpty(stationName))
+ {
+ StationName.Text = stationName;
+ }
+
NavMapScreen.ForceNavMapUpdate();
+ UpdateBeaconList(mapUid);
+ }
+
+ public void OnFilterChanged(string newFilter)
+ {
+ foreach (var button in _buttons)
+ {
+ button.Visible = string.IsNullOrEmpty(newFilter) || (
+ !string.IsNullOrEmpty(button.Label) &&
+ button.Label.Contains(newFilter, StringComparison.OrdinalIgnoreCase)
+ );
+ };
+ }
+
+ public void UpdateBeaconList(EntityUid? mapUid)
+ {
+ BeaconButtons.Children.Clear();
+ _buttons.Clear();
+
+ if (!mapUid.HasValue)
+ return;
+
+ if (!_entMan.TryGetComponent(mapUid, out var navMap))
+ return;
+
+ foreach (var beacon in navMap.Beacons.Values)
+ {
+ var button = new StationMapBeaconControl(mapUid.Value, beacon);
+
+ button.OnPressed += NavMapScreen.CenterToCoordinates;
+
+ _buttons.Add(button);
+ }
+
+ _buttons.Sort();
+
+ foreach (var button in _buttons)
+ BeaconButtons.AddChild(button);
}
-}
+}
\ No newline at end of file
diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
index b2e3fe1de50..4505e7d2ea1 100644
--- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
+++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
@@ -158,7 +158,7 @@ public bool CheckRoleRequirements(HashSet? requirements, Humanoi
reasons.Add(jobReason.ToMarkup());
}
- reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkup(string.Join('\n', reasons));
+ reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkupOrThrow(string.Join('\n', reasons));
return reason == null;
}
diff --git a/Content.Client/Power/APC/ApcBoundUserInterface.cs b/Content.Client/Power/APC/ApcBoundUserInterface.cs
index 759a5949ba6..a790c5d984a 100644
--- a/Content.Client/Power/APC/ApcBoundUserInterface.cs
+++ b/Content.Client/Power/APC/ApcBoundUserInterface.cs
@@ -19,8 +19,8 @@ public ApcBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
protected override void Open()
{
base.Open();
-
_menu = this.CreateWindow();
+ _menu.SetEntity(Owner);
_menu.OnBreaker += BreakerPressed;
}
diff --git a/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs
index 5a082485a5a..a6a20958f53 100644
--- a/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs
+++ b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs
@@ -18,9 +18,6 @@ protected override void OnActivate(Entity e
return;
}
- if (TryComp(ent.Owner, out var panel) && panel.Open)
- return;
-
_popup.PopupClient(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent.Owner)), args.User, args.User);
args.Cancel();
}
diff --git a/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
index 61e20f751ca..ebf6c18c953 100644
--- a/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
+++ b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using Content.Client.Power.Components;
using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
@@ -27,4 +28,16 @@ private void OnHandleState(EntityUid uid, ApcPowerReceiverComponent component, r
component.Powered = state.Powered;
}
+
+ public override bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component)
+ {
+ if (component != null)
+ return true;
+
+ if (!TryComp(entity, out ApcPowerReceiverComponent? receiver))
+ return false;
+
+ component = receiver;
+ return true;
+ }
}
diff --git a/Content.Client/Power/Generator/GeneratorWindow.xaml.cs b/Content.Client/Power/Generator/GeneratorWindow.xaml.cs
index e975e5d466e..161482e0905 100644
--- a/Content.Client/Power/Generator/GeneratorWindow.xaml.cs
+++ b/Content.Client/Power/Generator/GeneratorWindow.xaml.cs
@@ -3,6 +3,7 @@
using Content.Shared.Power.Generator;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Network;
namespace Content.Client.Power.Generator;
diff --git a/Content.Client/Power/PowerCharge/PowerChargeBoundUserInterface.cs b/Content.Client/Power/PowerCharge/PowerChargeBoundUserInterface.cs
new file mode 100644
index 00000000000..7a36b8ddf59
--- /dev/null
+++ b/Content.Client/Power/PowerCharge/PowerChargeBoundUserInterface.cs
@@ -0,0 +1,38 @@
+using Content.Shared.Power;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Power.PowerCharge;
+
+public sealed class PowerChargeBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables]
+ private PowerChargeWindow? _window;
+
+ public PowerChargeBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ public void SetPowerSwitch(bool on)
+ {
+ SendMessage(new SwitchChargingMachineMessage(on));
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ if (!EntMan.TryGetComponent(Owner, out PowerChargeComponent? component))
+ return;
+
+ _window = this.CreateWindow();
+ _window.UpdateWindow(this, Loc.GetString(component.WindowTitle));
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+ if (state is not PowerChargeState chargeState)
+ return;
+
+ _window?.UpdateState(chargeState);
+ }
+}
diff --git a/Content.Client/Power/PowerCharge/PowerChargeComponent.cs b/Content.Client/Power/PowerCharge/PowerChargeComponent.cs
new file mode 100644
index 00000000000..ab5baa4e2f5
--- /dev/null
+++ b/Content.Client/Power/PowerCharge/PowerChargeComponent.cs
@@ -0,0 +1,10 @@
+using Content.Shared.Power;
+
+namespace Content.Client.Power.PowerCharge;
+
+///
+[RegisterComponent]
+public sealed partial class PowerChargeComponent : SharedPowerChargeComponent
+{
+
+}
diff --git a/Content.Client/Power/PowerCharge/PowerChargeWindow.xaml b/Content.Client/Power/PowerCharge/PowerChargeWindow.xaml
new file mode 100644
index 00000000000..4e61255326e
--- /dev/null
+++ b/Content.Client/Power/PowerCharge/PowerChargeWindow.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Power/PowerCharge/PowerChargeWindow.xaml.cs b/Content.Client/Power/PowerCharge/PowerChargeWindow.xaml.cs
new file mode 100644
index 00000000000..6739e24c208
--- /dev/null
+++ b/Content.Client/Power/PowerCharge/PowerChargeWindow.xaml.cs
@@ -0,0 +1,72 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Power;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Power.PowerCharge;
+
+[GenerateTypedNameReferences]
+public sealed partial class PowerChargeWindow : FancyWindow
+{
+ private readonly ButtonGroup _buttonGroup = new();
+
+ public PowerChargeWindow()
+ {
+ RobustXamlLoader.Load(this);
+
+ OnButton.Group = _buttonGroup;
+ OffButton.Group = _buttonGroup;
+ }
+
+ public void UpdateWindow(PowerChargeBoundUserInterface bui, string title)
+ {
+ Title = title;
+
+ OnButton.OnPressed += _ => bui.SetPowerSwitch(true);
+ OffButton.OnPressed += _ => bui.SetPowerSwitch(false);
+
+ EntityView.SetEntity(bui.Owner);
+ }
+
+ public void UpdateState(PowerChargeState state)
+ {
+ if (state.On)
+ OnButton.Pressed = true;
+ else
+ OffButton.Pressed = true;
+
+ PowerLabel.Text = Loc.GetString(
+ "power-charge-window-power-label",
+ ("draw", state.PowerDraw),
+ ("max", state.PowerDrawMax));
+
+ PowerLabel.SetOnlyStyleClass(MathHelper.CloseTo(state.PowerDraw, state.PowerDrawMax) ? "Good" : "Caution");
+
+ ChargeBar.Value = state.Charge;
+ ChargeText.Text = (state.Charge / 255f).ToString("P0");
+ StatusLabel.Text = Loc.GetString(state.PowerStatus switch
+ {
+ PowerChargePowerStatus.Off => "power-charge-window-status-off",
+ PowerChargePowerStatus.Discharging => "power-charge-window-status-discharging",
+ PowerChargePowerStatus.Charging => "power-charge-window-status-charging",
+ PowerChargePowerStatus.FullyCharged => "power-charge-window-status-fully-charged",
+ _ => throw new ArgumentOutOfRangeException()
+ });
+
+ StatusLabel.SetOnlyStyleClass(state.PowerStatus switch
+ {
+ PowerChargePowerStatus.Off => "Danger",
+ PowerChargePowerStatus.Discharging => "Caution",
+ PowerChargePowerStatus.Charging => "Caution",
+ PowerChargePowerStatus.FullyCharged => "Good",
+ _ => throw new ArgumentOutOfRangeException()
+ });
+
+ EtaLabel.Text = state.EtaSeconds >= 0
+ ? Loc.GetString("power-charge-window-eta-value", ("left", TimeSpan.FromSeconds(state.EtaSeconds)))
+ : Loc.GetString("power-charge-window-eta-none");
+
+ EtaLabel.SetOnlyStyleClass(state.EtaSeconds >= 0 ? "Caution" : "Disabled");
+ }
+}
diff --git a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
index d9952992070..3f7ccfb903b 100644
--- a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
+++ b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
@@ -309,7 +309,7 @@ private void UpdateWarningLabel(PowerMonitoringFlags flags)
BorderThickness = new Thickness(2),
};
- msg.AddMarkup(Loc.GetString("power-monitoring-window-rogue-power-consumer"));
+ msg.AddMarkupOrThrow(Loc.GetString("power-monitoring-window-rogue-power-consumer"));
SystemWarningPanel.Visible = true;
}
@@ -322,7 +322,7 @@ private void UpdateWarningLabel(PowerMonitoringFlags flags)
BorderThickness = new Thickness(2),
};
- msg.AddMarkup(Loc.GetString("power-monitoring-window-power-net-abnormalities"));
+ msg.AddMarkupOrThrow(Loc.GetString("power-monitoring-window-power-net-abnormalities"));
SystemWarningPanel.Visible = true;
}
diff --git a/Content.Client/Power/PowerMonitoringWindow.xaml.cs b/Content.Client/Power/PowerMonitoringWindow.xaml.cs
index 9e380bcd2d3..a865d8e4151 100644
--- a/Content.Client/Power/PowerMonitoringWindow.xaml.cs
+++ b/Content.Client/Power/PowerMonitoringWindow.xaml.cs
@@ -167,7 +167,7 @@ public void ShowEntites
{
var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
- var blip = new NavMapBlip(monitorCoords.Value, _transformSystem.ToMapCoordinates(monitorCoords.Value), texture, Color.Cyan, true, false); // Frontier modification: add map coords as 2nd arg
+ var blip = new NavMapBlip(monitorCoords.Value, texture, Color.Cyan, true, false);
NavMap.TrackedEntities[mon] = blip;
}
@@ -231,7 +231,7 @@ private void AddTrackedEntityToNavMap(NetEntity netEntity, PowerMonitoringDevice
if (_focusEntity != null && usedEntity != _focusEntity && !entitiesOfInterest.Contains(usedEntity.Value))
modulator = Color.DimGray;
- var blip = new NavMapBlip(coords, coords.ToMap(_entManager, _transformSystem), _spriteSystem.Frame0(texture), color * modulator, blink); //Frontier modification
+ var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color * modulator, blink);
NavMap.TrackedEntities[netEntity] = blip;
}
diff --git a/Content.Client/Radio/Ui/IntercomMenu.xaml.cs b/Content.Client/Radio/Ui/IntercomMenu.xaml.cs
index 20d2e4a3e54..f66b3db000e 100644
--- a/Content.Client/Radio/Ui/IntercomMenu.xaml.cs
+++ b/Content.Client/Radio/Ui/IntercomMenu.xaml.cs
@@ -1,3 +1,4 @@
+using System.Threading.Channels;
using Content.Client.UserInterface.Controls;
using Content.Shared.Radio.Components;
using Robust.Client.AutoGenerated;
diff --git a/Content.Client/Replay/ContentReplayPlaybackManager.cs b/Content.Client/Replay/ContentReplayPlaybackManager.cs
index f90731bfa75..b96eae44e9d 100644
--- a/Content.Client/Replay/ContentReplayPlaybackManager.cs
+++ b/Content.Client/Replay/ContentReplayPlaybackManager.cs
@@ -1,10 +1,8 @@
-using System.IO.Compression;
using Content.Client.Administration.Managers;
using Content.Client.Launcher;
using Content.Client.MainMenu;
using Content.Client.Replay.Spectator;
using Content.Client.Replay.UI.Loading;
-using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.Chat;
using Content.Shared.Chat;
using Content.Shared.Effects;
@@ -26,8 +24,6 @@
using Robust.Client.State;
using Robust.Client.Timing;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
@@ -60,7 +56,7 @@ public sealed class ContentReplayPlaybackManager
public bool IsScreenshotMode = false;
private bool _initialized;
-
+
///
/// Most recently loaded file, for re-attempting the load with error tolerance.
/// Required because the zip reader auto-disposes and I'm too lazy to change it so that
@@ -96,32 +92,17 @@ private void OnFinishedLoading(Exception? exception)
return;
}
- ReturnToDefaultState();
-
- // Show a popup window with the error message
- var text = Loc.GetString("replay-loading-failed", ("reason", exception));
- var box = new BoxContainer
- {
- Orientation = BoxContainer.LayoutOrientation.Vertical,
- Children = {new Label {Text = text}}
- };
+ if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
+ _client.StopSinglePlayer();
- var popup = new DefaultWindow { Title = "Error!" };
- popup.Contents.AddChild(box);
+ Action? retryAction = null;
+ Action? cancelAction = null;
- // Add button for attempting to re-load the replay while ignoring some errors.
- if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is {} last)
+ if (!_cfg.GetCVar(CVars.ReplayIgnoreErrors) && LastLoad is { } last)
{
- var button = new Button
- {
- Text = Loc.GetString("replay-loading-retry"),
- StyleClasses = { StyleBase.ButtonCaution }
- };
-
- button.OnPressed += _ =>
+ retryAction = () =>
{
_cfg.SetCVar(CVars.ReplayIgnoreErrors, true);
- popup.Dispose();
IReplayFileReader reader = last.Zip == null
? new ReplayFileReaderResources(_resMan, last.Folder)
@@ -129,11 +110,20 @@ private void OnFinishedLoading(Exception? exception)
_loadMan.LoadAndStartReplay(reader);
};
-
- box.AddChild(button);
}
- popup.OpenCentered();
+ // If we have an explicit menu to get back to (e.g. replay browser UI), show a cancel button.
+ if (DefaultState != null)
+ {
+ cancelAction = () =>
+ {
+ _stateMan.RequestStateChange(DefaultState);
+ };
+ }
+
+ // Switch to a new game state to present the error and cancel/retry options.
+ var state = _stateMan.RequestStateChange();
+ state.SetData(exception, cancelAction, retryAction);
}
public void ReturnToDefaultState()
diff --git a/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs b/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs
new file mode 100644
index 00000000000..223895eb29c
--- /dev/null
+++ b/Content.Client/Replay/UI/Loading/ReplayLoadingFailed.cs
@@ -0,0 +1,36 @@
+using Content.Client.Stylesheets;
+using Robust.Client.State;
+using Robust.Client.UserInterface;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Replay.UI.Loading;
+
+///
+/// State used to display an error message if a replay failed to load.
+///
+///
+///
+public sealed class ReplayLoadingFailed : State
+{
+ [Dependency] private readonly IStylesheetManager _stylesheetManager = default!;
+ [Dependency] private readonly IUserInterfaceManager _userInterface = default!;
+
+ private ReplayLoadingFailedControl? _control;
+
+ public void SetData(Exception exception, Action? cancelPressed, Action? retryPressed)
+ {
+ DebugTools.Assert(_control != null);
+ _control.SetData(exception, cancelPressed, retryPressed);
+ }
+
+ protected override void Startup()
+ {
+ _control = new ReplayLoadingFailedControl(_stylesheetManager);
+ _userInterface.StateRoot.AddChild(_control);
+ }
+
+ protected override void Shutdown()
+ {
+ _control?.Orphan();
+ }
+}
diff --git a/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml b/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml
new file mode 100644
index 00000000000..5f77a66e535
--- /dev/null
+++ b/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml.cs b/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml.cs
new file mode 100644
index 00000000000..088c9a291a7
--- /dev/null
+++ b/Content.Client/Replay/UI/Loading/ReplayLoadingFailedControl.xaml.cs
@@ -0,0 +1,44 @@
+using Content.Client.Stylesheets;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Replay.UI.Loading;
+
+[GenerateTypedNameReferences]
+public sealed partial class ReplayLoadingFailedControl : Control
+{
+ public ReplayLoadingFailedControl(IStylesheetManager stylesheet)
+ {
+ RobustXamlLoader.Load(this);
+
+ Stylesheet = stylesheet.SheetSpace;
+ LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
+ }
+
+ public void SetData(Exception exception, Action? cancelPressed, Action? retryPressed)
+ {
+ ReasonLabel.SetMessage(
+ FormattedMessage.FromUnformatted(Loc.GetString("replay-loading-failed", ("reason", exception))));
+
+ if (cancelPressed != null)
+ {
+ CancelButton.Visible = true;
+ CancelButton.OnPressed += _ =>
+ {
+ cancelPressed();
+ };
+ }
+
+ if (retryPressed != null)
+ {
+ RetryButton.Visible = true;
+ RetryButton.OnPressed += _ =>
+ {
+ retryPressed();
+ };
+ }
+ }
+}
diff --git a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs
index 87d7e62c392..06e5674d9cb 100644
--- a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs
+++ b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs
@@ -128,12 +128,12 @@ private void PopulateData()
};
var text = new FormattedMessage();
- text.PushMarkup(Loc.GetString("robotics-console-model", ("name", model)));
- text.AddMarkup(Loc.GetString("robotics-console-designation"));
+ text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-model", ("name", model))}\n");
+ text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation"));
text.AddText($" {data.Name}\n"); // prevent players trolling by naming borg [color=red]satan[/color]
- text.PushMarkup(Loc.GetString("robotics-console-battery", ("charge", (int) (data.Charge * 100f)), ("color", batteryColor)));
- text.PushMarkup(Loc.GetString("robotics-console-brain", ("brain", data.HasBrain)));
- text.AddMarkup(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount)));
+ text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-battery", ("charge", (int)(data.Charge * 100f)), ("color", batteryColor))}\n");
+ text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-brain", ("brain", data.HasBrain))}\n");
+ text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount)));
BorgInfo.SetMessage(text);
// how the turntables
diff --git a/Content.Client/Roles/RoleCodewordSystem.cs b/Content.Client/Roles/RoleCodewordSystem.cs
new file mode 100644
index 00000000000..8cc2e93099f
--- /dev/null
+++ b/Content.Client/Roles/RoleCodewordSystem.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Roles.RoleCodeword;
+
+namespace Content.Client.Roles;
+
+public sealed class RoleCodewordSystem : SharedRoleCodewordSystem
+{
+
+}
diff --git a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs
index 9c9f83a4275..7108e4cca8f 100644
--- a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs
+++ b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs
@@ -61,9 +61,9 @@ private BoxContainer MakeRoundEndSummaryTab(string gamemode, string roundEnd, Ti
//Gamemode Name
var gamemodeLabel = new RichTextLabel();
var gamemodeMessage = new FormattedMessage();
- gamemodeMessage.AddMarkup(Loc.GetString("round-end-summary-window-round-id-label", ("roundId", roundId)));
+ gamemodeMessage.AddMarkupOrThrow(Loc.GetString("round-end-summary-window-round-id-label", ("roundId", roundId)));
gamemodeMessage.AddText(" ");
- gamemodeMessage.AddMarkup(Loc.GetString("round-end-summary-window-gamemode-name-label", ("gamemode", gamemode)));
+ gamemodeMessage.AddMarkupOrThrow(Loc.GetString("round-end-summary-window-gamemode-name-label", ("gamemode", gamemode)));
gamemodeLabel.SetMessage(gamemodeMessage);
roundEndSummaryContainer.AddChild(gamemodeLabel);
diff --git a/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs b/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
index 7e99426f87b..d691f9acef3 100644
--- a/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
+++ b/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
@@ -53,9 +53,9 @@ protected override void UpdateState(BoundUserInterfaceState state)
option.Claimed = current.ActiveSeed == seed;
var claimIndex = i;
- option.ClaimPressed += args =>
+ option.ClaimPressed += _ =>
{
- SendMessage(new MagnetClaimOfferEvent()
+ SendMessage(new MagnetClaimOfferEvent
{
Index = claimIndex
});
@@ -72,20 +72,20 @@ protected override void UpdateState(BoundUserInterfaceState state)
{
var count = asteroid.MarkerLayers[resource];
- var container = new BoxContainer()
+ var container = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
};
- var resourceLabel = new Label()
+ var resourceLabel = new Label
{
Text = Loc.GetString("salvage-magnet-resources",
("resource", resource)),
HorizontalAlignment = Control.HAlignment.Left,
};
- var countLabel = new Label()
+ var countLabel = new Label
{
Text = Loc.GetString("salvage-magnet-resources-count", ("count", count)),
HorizontalAlignment = Control.HAlignment.Right,
@@ -98,6 +98,9 @@ protected override void UpdateState(BoundUserInterfaceState state)
option.AddContent(container);
}
+ break;
+ case DebrisOffering debris:
+ option.Title = Loc.GetString($"salvage-magnet-debris-{debris.Id}");
break;
case SalvageOffering salvage:
option.Title = Loc.GetString($"salvage-map-wreck");
diff --git a/Content.Client/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs b/Content.Client/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs
index e5379847448..9365d8fe94c 100644
--- a/Content.Client/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs
+++ b/Content.Client/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs
@@ -51,6 +51,7 @@ private void Populate(List availablePrototypes, List unavailable
_menu.PopulateProducts(availablePrototypes, unavailablePrototypes, freeListings, validId);
_menu.PopulateCategories(availablePrototypes, unavailablePrototypes);
_menu.PopulateClasses(availablePrototypes, unavailablePrototypes);
+ _menu.PopulateEngines(availablePrototypes, unavailablePrototypes);
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml b/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml
index c5e8a23d3ef..2bde8483a30 100644
--- a/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml
+++ b/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml
@@ -1,8 +1,8 @@
+ SetSize="640 500"
+ MinSize="640 300">
@@ -32,6 +32,9 @@
+
_categoryStrings = new();
private readonly List _classStrings = new();
+ private readonly List _engineStrings = new();
private VesselSize? _category;
private VesselClass? _class;
+ private VesselEngine? _engine;
private List _lastAvailableProtos = new();
private List _lastUnavailableProtos = new();
@@ -39,6 +41,7 @@ public ShipyardConsoleMenu(ShipyardConsoleBoundUserInterface owner)
SearchBar.OnTextChanged += OnSearchBarTextChanged;
Categories.OnItemSelected += OnCategoryItemSelected;
Classes.OnItemSelected += OnClassItemSelected;
+ Engines.OnItemSelected += OnEngineItemSelected;
SellShipButton.OnPressed += (args) => { OnSellShip?.Invoke(args); };
}
@@ -55,6 +58,12 @@ private void OnClassItemSelected(OptionButton.ItemSelectedEventArgs args)
PopulateProducts(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
}
+ private void OnEngineItemSelected(OptionButton.ItemSelectedEventArgs args)
+ {
+ SetEngineText(args.Id);
+ PopulateProducts(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
+ }
+
private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
{
PopulateProducts(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
@@ -70,6 +79,11 @@ private void SetClassText(int id)
_class = id == 0 ? null : _classStrings[id];
Classes.SelectId(id);
}
+ private void SetEngineText(int id)
+ {
+ _engine = id == 0 ? null : _engineStrings[id];
+ Engines.SelectId(id);
+ }
///
/// Populates the list of products that will actually be shown, using the current filters.
///
@@ -115,6 +129,8 @@ private void AddVesselsToControls(IEnumerable vessels, string
continue;
if (_class != null && !prototype!.Classes.Contains(_class.Value))
continue;
+ if (_engine != null && !prototype!.Engines.Contains(_engine.Value))
+ continue;
if (search.Length > 0 && !prototype!.Name.ToLowerInvariant().Contains(search))
continue;
@@ -176,6 +192,9 @@ private void AddCategoriesFromPrototypes(IEnumerable prototypes)
}
}
+ ///
+ /// Populates the list classes that will actually be shown, using the current filters.
+ ///
public void PopulateClasses(List availablePrototypes, List unavailablePrototypes)
{
_classStrings.Clear();
@@ -215,6 +234,48 @@ private void AddClassesFromPrototypes(IEnumerable prototypes)
}
}
+ ///
+ /// Populates the list engines that will actually be shown, using the current filters.
+ ///
+ public void PopulateEngines(List availablePrototypes, List unavailablePrototypes)
+ {
+ _engineStrings.Clear();
+ Engines.Clear();
+
+ AddEnginesFromPrototypes(availablePrototypes);
+ AddEnginesFromPrototypes(unavailablePrototypes);
+
+ _engineStrings.Sort();
+
+ // Add "All" category at the top of the list
+ _engineStrings.Insert(0, VesselEngine.All);
+
+ foreach (var str in _engineStrings)
+ {
+ Engines.AddItem(Loc.GetString($"shipyard-console-engine-{str}"));
+ }
+ }
+
+ ///
+ /// Adds all ship engine power type from a list of vessel prototypes to the current control's list if they are missing.
+ ///
+ private void AddEnginesFromPrototypes(IEnumerable prototypes)
+ {
+ foreach (var protoId in prototypes)
+ {
+ if (!_protoManager.TryIndex(protoId, out var prototype))
+ continue;
+
+ foreach (var cl in prototype.Engines)
+ {
+ if (!_engineStrings.Contains(cl) && cl != VesselEngine.All)
+ {
+ _engineStrings.Add(cl);
+ }
+ }
+ }
+ }
+
public void UpdateState(ShipyardConsoleInterfaceState state)
{
BalanceLabel.Text = BankSystemExtensions.ToSpesoString(state.Balance);
diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs
index 8e5545d29c0..bbb04d09ade 100644
--- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs
@@ -37,8 +37,10 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
public bool ShowIFF { get; set; } = true;
public bool ShowIFFShuttles { get; set; } = true;
public bool ShowDocks { get; set; } = true;
+ public bool RotateWithEntity { get; set; } = true;
public float MaximumIFFDistance { get; set; } = -1f; // Frontier
+ public bool HideCoords { get; set; } = false; // Frontier
///
/// If present, called for every IFF. Must determine if it should or should not be shown.
@@ -117,6 +119,14 @@ public void UpdateState(NavInterfaceState state)
ActualRadarRange = Math.Clamp(ActualRadarRange, WorldMinRange, WorldMaxRange);
+ RotateWithEntity = state.RotateWithEntity;
+
+ // Frontier
+ if (state.MaxIffRange != null)
+ MaximumIFFDistance = state.MaxIffRange.Value;
+ HideCoords = state.HideCoords;
+ // End Frontier
+
_docks = state.Docks;
NfUpdateState(state); // Frontier Update State
@@ -148,7 +158,8 @@ protected override void Draw(DrawingHandleScreen handle)
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
var offset = _coordinates.Value.Position;
var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value);
- var (_, ourEntRot, ourEntMatrix) = _transform.GetWorldPositionRotationMatrix(_coordinates.Value.EntityId);
+ var ourEntRot = RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
+ var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert);
@@ -211,7 +222,9 @@ protected override void Draw(DrawingHandleScreen handle)
var gridMatrix = _transform.GetWorldMatrix(gUid);
var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert);
- var color = _shuttles.GetIFFColor(grid, self: false, iff);
+
+ var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
+ var coordColor = new Color(labelColor.R * 0.8f, labelColor.G * 0.8f, labelColor.B * 0.8f, 0.5f);
// Others default:
// Color.FromHex("#FFC000FF")
@@ -236,6 +249,7 @@ protected override void Draw(DrawingHandleScreen handle)
var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty);
gridCentre.Y = -gridCentre.Y;
+ // Frontier: IFF drawing functions
// The actual position in the UI. We offset the matrix position to render it off by half its width
// plus by the offset.
var uiPosition = ScalePosition(gridCentre) / UIScale;
@@ -269,12 +283,15 @@ protected override void Draw(DrawingHandleScreen handle)
var distance = gridCentre.Length();
- // Shows decimal when distance is < 50m, otherwise pointless to show it.
- var displayedDistance = distance < 50f ? $"{distance:0.0}" : distance < 1000 ? $"{distance:0}" : $"{distance / 1000:0.0}k";
- var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName)!, ("distance", displayedDistance));
-
if (!isOutsideRadarCircle || isDistantPOI || isMouseOver)
{
+ // Shows decimal when distance is < 50m, otherwise pointless to show it.
+ var displayedDistance = distance < 50f ? $"{distance:0.0}" : distance < 1000 ? $"{distance:0}" : $"{distance / 1000:0.0}k";
+ var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName)!, ("distance", displayedDistance));
+
+ var mapCoords = _transform.GetWorldPosition(gUid);
+ var coordsText = $"({mapCoords.X:0.0}, {mapCoords.Y:0.0})";
+
// Calculate unscaled offsets.
var labelDimensions = handle.GetDimensions(Font, labelText, 1f);
var blipSize = RadarBlipSize * 0.7f;
@@ -286,10 +303,23 @@ protected override void Draw(DrawingHandleScreen handle)
Y = -labelDimensions.Y / 2f
};
- handle.DrawString(Font, (uiPosition + labelOffset) * UIScale, labelText, UIScale, color);
+ handle.DrawString(Font, (uiPosition + labelOffset) * UIScale, labelText, UIScale, labelColor);
+ if (isMouseOver && !HideCoords)
+ {
+ var coordDimensions = handle.GetDimensions(Font, coordsText, 0.7f);
+ var coordOffset = new Vector2()
+ {
+ X = uiPosition.X > Width / 2f
+ ? -coordDimensions.X - blipSize / 0.7f // right align the text to left of the blip (0.7 needed for scale)
+ : blipSize, // left align the text to the right of the blip
+ Y = coordDimensions.Y / 2
+ };
+ handle.DrawString(Font, (uiPosition + coordOffset) * UIScale, coordsText, 0.7f * UIScale, coordColor);
+ }
}
- NfAddBlipToList(blipDataList, isOutsideRadarCircle, uiPosition, uiXCentre, uiYCentre, color); // Frontier code
+ NfAddBlipToList(blipDataList, isOutsideRadarCircle, uiPosition, uiXCentre, uiYCentre, labelColor); // Frontier code
+ // End Frontier: IFF drawing functions
}
// Frontier Don't skip drawing blips if they're out of range.
@@ -302,7 +332,7 @@ protected override void Draw(DrawingHandleScreen handle)
if (!gridAABB.Intersects(viewAABB))
continue;
- DrawGrid(handle, matty, grid, color);
+ DrawGrid(handle, matty, grid, labelColor);
DrawDocks(handle, gUid, matty);
}
}
diff --git a/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs b/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs
index a4d59d1f315..03c74032f73 100644
--- a/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs
+++ b/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs
@@ -6,7 +6,7 @@ namespace Content.Client.Silicons.Laws.SiliconLawEditUi;
public sealed class SiliconLawEui : BaseEui
{
- public readonly EntityManager _entityManager = default!;
+ private readonly EntityManager _entityManager;
private SiliconLawUi _siliconLawUi;
private EntityUid _target;
diff --git a/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs
new file mode 100644
index 00000000000..68318305a0c
--- /dev/null
+++ b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs
@@ -0,0 +1,28 @@
+using Content.Shared.Silicons.StationAi;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Silicons.StationAi;
+
+public sealed class StationAiBoundUserInterface : BoundUserInterface
+{
+ private StationAiMenu? _menu;
+
+ public StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _menu = this.CreateWindow();
+ _menu.Track(Owner);
+
+ _menu.OnAiRadial += args =>
+ {
+ SendPredictedMessage(new StationAiRadialMessage()
+ {
+ Event = args,
+ });
+ };
+ }
+}
diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml b/Content.Client/Silicons/StationAi/StationAiMenu.xaml
new file mode 100644
index 00000000000..d56fc832898
--- /dev/null
+++ b/Content.Client/Silicons/StationAi/StationAiMenu.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs
new file mode 100644
index 00000000000..b152f5ead8b
--- /dev/null
+++ b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs
@@ -0,0 +1,127 @@
+using System.Numerics;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Silicons.StationAi;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Silicons.StationAi;
+
+[GenerateTypedNameReferences]
+public sealed partial class StationAiMenu : RadialMenu
+{
+ [Dependency] private readonly IClyde _clyde = default!;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+
+ public event Action? OnAiRadial;
+
+ private EntityUid _tracked;
+
+ public StationAiMenu()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+ }
+
+ public void Track(EntityUid owner)
+ {
+ _tracked = owner;
+
+ if (!_entManager.EntityExists(_tracked))
+ {
+ Close();
+ return;
+ }
+
+ BuildButtons();
+ UpdatePosition();
+ }
+
+ private void BuildButtons()
+ {
+ var ev = new GetStationAiRadialEvent();
+ _entManager.EventBus.RaiseLocalEvent(_tracked, ref ev);
+
+ var main = FindControl("Main");
+ main.DisposeAllChildren();
+ var sprites = _entManager.System();
+
+ foreach (var action in ev.Actions)
+ {
+ // TODO: This radial boilerplate is quite annoying
+ var button = new StationAiMenuButton(action.Event)
+ {
+ StyleClasses = { "RadialMenuButton" },
+ SetSize = new Vector2(64f, 64f),
+ ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null,
+ };
+
+ if (action.Sprite != null)
+ {
+ var texture = sprites.Frame0(action.Sprite);
+ var scale = Vector2.One;
+
+ if (texture.Width <= 32)
+ {
+ scale *= 2;
+ }
+
+ var tex = new TextureRect
+ {
+ VerticalAlignment = VAlignment.Center,
+ HorizontalAlignment = HAlignment.Center,
+ Texture = texture,
+ TextureScale = scale,
+ };
+
+ button.AddChild(tex);
+ }
+
+ button.OnPressed += args =>
+ {
+ OnAiRadial?.Invoke(action.Event);
+ Close();
+ };
+ main.AddChild(button);
+ }
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+ UpdatePosition();
+ }
+
+ private void UpdatePosition()
+ {
+ if (!_entManager.TryGetComponent(_tracked, out TransformComponent? xform))
+ {
+ Close();
+ return;
+ }
+
+ if (!xform.Coordinates.IsValid(_entManager))
+ {
+ Close();
+ return;
+ }
+
+ var coords = _entManager.System().GetSpriteScreenCoordinates((_tracked, null, xform));
+
+ if (!coords.IsValid)
+ {
+ Close();
+ return;
+ }
+
+ OpenScreenAt(coords.Position, _clyde);
+ }
+}
+
+public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButton
+{
+ public BaseStationAiAction Action = action;
+}
diff --git a/Content.Client/Silicons/StationAi/StationAiOverlay.cs b/Content.Client/Silicons/StationAi/StationAiOverlay.cs
new file mode 100644
index 00000000000..15a8a3a63fe
--- /dev/null
+++ b/Content.Client/Silicons/StationAi/StationAiOverlay.cs
@@ -0,0 +1,128 @@
+using System.Numerics;
+using Content.Shared.Silicons.StationAi;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Silicons.StationAi;
+
+public sealed class StationAiOverlay : Overlay
+{
+ [Dependency] private readonly IClyde _clyde = default!;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+
+ private readonly HashSet _visibleTiles = new();
+
+ private IRenderTexture? _staticTexture;
+ private IRenderTexture? _stencilTexture;
+
+ private float _updateRate = 1f / 30f;
+ private float _accumulator;
+
+ public StationAiOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (_stencilTexture?.Texture.Size != args.Viewport.Size)
+ {
+ _staticTexture?.Dispose();
+ _stencilTexture?.Dispose();
+ _stencilTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil");
+ _staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size,
+ new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
+ name: "station-ai-static");
+ }
+
+ var worldHandle = args.WorldHandle;
+
+ var worldBounds = args.WorldBounds;
+
+ var playerEnt = _player.LocalEntity;
+ _entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform);
+ var gridUid = playerXform?.GridUid ?? EntityUid.Invalid;
+ _entManager.TryGetComponent(gridUid, out MapGridComponent? grid);
+ _entManager.TryGetComponent(gridUid, out BroadphaseComponent? broadphase);
+
+ var invMatrix = args.Viewport.GetWorldToLocalMatrix();
+ _accumulator -= (float) _timing.FrameTime.TotalSeconds;
+
+ if (grid != null && broadphase != null)
+ {
+ var lookups = _entManager.System();
+ var xforms = _entManager.System();
+
+ if (_accumulator <= 0f)
+ {
+ _accumulator = MathF.Max(0f, _accumulator + _updateRate);
+ _visibleTiles.Clear();
+ _entManager.System().GetView((gridUid, broadphase, grid), worldBounds, _visibleTiles);
+ }
+
+ var gridMatrix = xforms.GetWorldMatrix(gridUid);
+ var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
+
+ // Draw visible tiles to stencil
+ worldHandle.RenderInRenderTarget(_stencilTexture!, () =>
+ {
+ worldHandle.SetTransform(matty);
+
+ foreach (var tile in _visibleTiles)
+ {
+ var aabb = lookups.GetLocalBounds(tile, grid.TileSize);
+ worldHandle.DrawRect(aabb, Color.White);
+ }
+ },
+ Color.Transparent);
+
+ // Once this is gucci optimise rendering.
+ worldHandle.RenderInRenderTarget(_staticTexture!,
+ () =>
+ {
+ worldHandle.SetTransform(invMatrix);
+ var shader = _proto.Index("CameraStatic").Instance();
+ worldHandle.UseShader(shader);
+ worldHandle.DrawRect(worldBounds, Color.White);
+ },
+ Color.Black);
+ }
+ // Not on a grid
+ else
+ {
+ worldHandle.RenderInRenderTarget(_stencilTexture!, () =>
+ {
+ },
+ Color.Transparent);
+
+ worldHandle.RenderInRenderTarget(_staticTexture!,
+ () =>
+ {
+ worldHandle.SetTransform(Matrix3x2.Identity);
+ worldHandle.DrawRect(worldBounds, Color.Black);
+ }, Color.Black);
+ }
+
+ // Use the lighting as a mask
+ worldHandle.UseShader(_proto.Index("StencilMask").Instance());
+ worldHandle.DrawTextureRect(_stencilTexture!.Texture, worldBounds);
+
+ // Draw the static
+ worldHandle.UseShader(_proto.Index("StencilDraw").Instance());
+ worldHandle.DrawTextureRect(_staticTexture!.Texture, worldBounds);
+
+ worldHandle.SetTransform(Matrix3x2.Identity);
+ worldHandle.UseShader(null);
+
+ }
+}
diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
new file mode 100644
index 00000000000..bf6b65a9697
--- /dev/null
+++ b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
@@ -0,0 +1,30 @@
+using Content.Shared.Doors.Components;
+using Content.Shared.Silicons.StationAi;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Silicons.StationAi;
+
+public sealed partial class StationAiSystem
+{
+ private void InitializeAirlock()
+ {
+ SubscribeLocalEvent(OnDoorBoltGetRadial);
+ }
+
+ private void OnDoorBoltGetRadial(Entity ent, ref GetStationAiRadialEvent args)
+ {
+ args.Actions.Add(new StationAiRadial()
+ {
+ Sprite = ent.Comp.BoltsDown ?
+ new SpriteSpecifier.Rsi(
+ new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") :
+ new SpriteSpecifier.Rsi(
+ new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"),
+ Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"),
+ Event = new StationAiBoltEvent()
+ {
+ Bolted = !ent.Comp.BoltsDown,
+ }
+ });
+ }
+}
diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs
new file mode 100644
index 00000000000..cf2f6136207
--- /dev/null
+++ b/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs
@@ -0,0 +1,32 @@
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Light.Components;
+using Content.Shared.Silicons.StationAi;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Silicons.StationAi;
+
+public sealed partial class StationAiSystem
+{
+ // Used for surveillance camera lights
+
+ private void InitializePowerToggle()
+ {
+ SubscribeLocalEvent(OnLightGetRadial);
+ }
+
+ private void OnLightGetRadial(Entity ent, ref GetStationAiRadialEvent args)
+ {
+ if (!TryComp(ent.Owner, out ItemToggleComponent? toggle))
+ return;
+
+ args.Actions.Add(new StationAiRadial()
+ {
+ Tooltip = Loc.GetString("toggle-light"),
+ Sprite = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
+ Event = new StationAiLightEvent()
+ {
+ Enabled = !toggle.Activated
+ }
+ });
+ }
+}
diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.cs b/Content.Client/Silicons/StationAi/StationAiSystem.cs
new file mode 100644
index 00000000000..ab9ace3c1d5
--- /dev/null
+++ b/Content.Client/Silicons/StationAi/StationAiSystem.cs
@@ -0,0 +1,80 @@
+using Content.Shared.Silicons.StationAi;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Player;
+
+namespace Content.Client.Silicons.StationAi;
+
+public sealed partial class StationAiSystem : SharedStationAiSystem
+{
+ [Dependency] private readonly IOverlayManager _overlayMgr = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+
+ private StationAiOverlay? _overlay;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ InitializeAirlock();
+ InitializePowerToggle();
+
+ SubscribeLocalEvent(OnAiAttached);
+ SubscribeLocalEvent(OnAiDetached);
+ SubscribeLocalEvent(OnAiOverlayInit);
+ SubscribeLocalEvent(OnAiOverlayRemove);
+ }
+
+ private void OnAiOverlayInit(Entity ent, ref ComponentInit args)
+ {
+ var attachedEnt = _player.LocalEntity;
+
+ if (attachedEnt != ent.Owner)
+ return;
+
+ AddOverlay();
+ }
+
+ private void OnAiOverlayRemove(Entity ent, ref ComponentRemove args)
+ {
+ var attachedEnt = _player.LocalEntity;
+
+ if (attachedEnt != ent.Owner)
+ return;
+
+ RemoveOverlay();
+ }
+
+ private void AddOverlay()
+ {
+ if (_overlay != null)
+ return;
+
+ _overlay = new StationAiOverlay();
+ _overlayMgr.AddOverlay(_overlay);
+ }
+
+ private void RemoveOverlay()
+ {
+ if (_overlay == null)
+ return;
+
+ _overlayMgr.RemoveOverlay(_overlay);
+ _overlay = null;
+ }
+
+ private void OnAiAttached(Entity ent, ref LocalPlayerAttachedEvent args)
+ {
+ AddOverlay();
+ }
+
+ private void OnAiDetached(Entity ent, ref LocalPlayerDetachedEvent args)
+ {
+ RemoveOverlay();
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _overlayMgr.RemoveOverlay();
+ }
+}
diff --git a/Content.Client/StationRecords/GeneralRecord.xaml.cs b/Content.Client/StationRecords/GeneralRecord.xaml.cs
index 6a2622fba9e..e84c2dc0982 100644
--- a/Content.Client/StationRecords/GeneralRecord.xaml.cs
+++ b/Content.Client/StationRecords/GeneralRecord.xaml.cs
@@ -2,6 +2,7 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Enums;
namespace Content.Client.StationRecords;
diff --git a/Content.Client/Storage/Components/StorageContainerVisualsComponent.cs b/Content.Client/Storage/Components/StorageContainerVisualsComponent.cs
index 9f07867da85..9ef6c65e890 100644
--- a/Content.Client/Storage/Components/StorageContainerVisualsComponent.cs
+++ b/Content.Client/Storage/Components/StorageContainerVisualsComponent.cs
@@ -1,4 +1,5 @@
using Content.Client.Chemistry.Visualizers;
+using Content.Shared.Chemistry.Components;
namespace Content.Client.Storage.Components;
diff --git a/Content.Client/Storage/StorageBoundUserInterface.cs b/Content.Client/Storage/StorageBoundUserInterface.cs
index 899df30f7fc..b90977cbb4d 100644
--- a/Content.Client/Storage/StorageBoundUserInterface.cs
+++ b/Content.Client/Storage/StorageBoundUserInterface.cs
@@ -11,6 +11,8 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
private readonly StorageSystem _storage;
+ [Obsolete] public override bool DeferredClose => false;
+
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
diff --git a/Content.Client/Store/Ui/StoreBoundUserInterface.cs b/Content.Client/Store/Ui/StoreBoundUserInterface.cs
index 7ed67f7b5dd..8c48258de00 100644
--- a/Content.Client/Store/Ui/StoreBoundUserInterface.cs
+++ b/Content.Client/Store/Ui/StoreBoundUserInterface.cs
@@ -19,7 +19,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
private string _search = string.Empty;
[ViewVariables]
- private HashSet _listings = new();
+ private HashSet _listings = new();
public StoreBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
@@ -33,7 +33,7 @@ protected override void Open()
_menu.OnListingButtonPressed += (_, listing) =>
{
- SendMessage(new StoreBuyListingMessage(listing));
+ SendMessage(new StoreBuyListingMessage(listing.ID));
};
_menu.OnCategoryButtonPressed += (_, category) =>
@@ -68,6 +68,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_listings = msg.Listings;
_menu?.UpdateBalance(msg.Balance);
+
UpdateListingsWithSearchFilter();
_menu?.SetFooterVisibility(msg.ShowFooter);
_menu?.UpdateRefund(msg.AllowRefund);
@@ -80,7 +81,7 @@ private void UpdateListingsWithSearchFilter()
if (_menu == null)
return;
- var filteredListings = new HashSet(_listings);
+ var filteredListings = new HashSet(_listings);
if (!string.IsNullOrEmpty(_search))
{
filteredListings.RemoveWhere(listingData => !ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search) &&
diff --git a/Content.Client/Store/Ui/StoreListingControl.xaml b/Content.Client/Store/Ui/StoreListingControl.xaml
index 12b4d7b5b30..3142f1cb061 100644
--- a/Content.Client/Store/Ui/StoreListingControl.xaml
+++ b/Content.Client/Store/Ui/StoreListingControl.xaml
@@ -2,6 +2,8 @@
+