From 8a2e13cb0af2ac1db5481939e4c5fd10061a5a17 Mon Sep 17 00:00:00 2001 From: Daniel <77834935+Orvex07@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:00:52 +0100 Subject: [PATCH] CrewMonitoring (#3788) --- .../CrewMonitoringBoundUserInterface.cs | 2 - .../CrewMonitoring/CrewMonitoringWindow.xaml | 7 - .../CrewMonitoringWindow.xaml.cs | 41 -- ...igmedicCrewMonitoringBoundUserInterface.cs | 58 -- .../BSOCrewMonitoringBoundUserInterface.cs | 73 --- ...SunriseCrewMonitoringBoundUserInterface.cs | 46 ++ .../SunriseCrewMonitoringWindow.xaml | 58 ++ .../SunriseCrewMonitoringWindow.xaml.cs | 504 ++++++++++++++++++ .../CrewMonitoringConsoleSystem.cs | 3 + .../CrewMonitoringConsoleSystem.Filter.cs | 84 +++ .../CrewMonitoringFilterComponent.cs | 22 + .../handheld_emergency_crew_monitor.ftl | 5 + .../_sunrise/medical/crew-monitoring.ftl | 1 + .../medical/handheld_bso_crew_monitor.ftl | 4 +- .../handheld_emergency_crew_monitor.ftl | 5 + .../_sunrise/medical/crew-monitoring.ftl | 2 + .../Roles/Jobs/Medical/paramedic.yml | 2 +- .../handheld_brigmedic_crew_monitor.yml | 7 +- .../Medical/handheld_bso_crew_monitor.yml | 6 +- .../handheld_emergency_crew_monitor.yml | 39 ++ .../icon.png | Bin .../meta.json | 0 .../scanner.png | Bin .../icon.png | Bin 0 -> 553 bytes .../meta.json | 33 ++ .../scanner-inhand-left.png | Bin 0 -> 638 bytes .../scanner-inhand-right.png | Bin 0 -> 630 bytes .../scanner.png | Bin 0 -> 734 bytes 28 files changed, 815 insertions(+), 187 deletions(-) delete mode 100644 Content.Client/_Starlight/Medical/CrewMonitoring/Brigmedic/BrigmedicCrewMonitoringBoundUserInterface.cs delete mode 100644 Content.Client/_Sunrise/Medical/CrewMonitoring/BSOCrewMonitoringBoundUserInterface.cs create mode 100644 Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringBoundUserInterface.cs create mode 100644 Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringWindow.xaml create mode 100644 Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringWindow.xaml.cs create mode 100644 Content.Server/_Sunrise/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.Filter.cs create mode 100644 Content.Shared/_Sunrise/Medical/CrewMonitoring/CrewMonitoringFilterComponent.cs create mode 100644 Resources/Locale/en-US/_prototypes/_sunrise/entities/objects/specific/medical/handheld_emergency_crew_monitor.ftl create mode 100644 Resources/Locale/ru-RU/_prototypes/_sunrise/entities/objects/specific/medical/handheld_emergency_crew_monitor.ftl rename Resources/Prototypes/{_Starlight => _Sunrise}/Entities/Objects/Specific/Medical/handheld_brigmedic_crew_monitor.yml (82%) create mode 100644 Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_emergency_crew_monitor.yml rename Resources/Textures/{_Starlight => _Sunrise}/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/icon.png (100%) rename Resources/Textures/{_Starlight => _Sunrise}/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/meta.json (100%) rename Resources/Textures/{_Starlight => _Sunrise}/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/scanner.png (100%) create mode 100644 Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/icon.png create mode 100644 Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/meta.json create mode 100644 Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/scanner-inhand-left.png create mode 100644 Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/scanner-inhand-right.png create mode 100644 Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/scanner.png diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs index a3a03cd14e..550d0fcdfe 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs @@ -31,7 +31,6 @@ public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface _menu = this.CreateWindow(); _menu.Set(stationName, gridUid); - _menu.SetBoundUserInterface(this); } protected override void UpdateState(BoundUserInterfaceState state) @@ -43,7 +42,6 @@ public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface case CrewMonitoringState st: EntMan.TryGetComponent(Owner, out var xform); _menu?.ShowSensors(st.Sensors, Owner, xform?.Coordinates); - _menu?.UpdateCorpseAlertToggle(st.CorpseAlertEnabled); break; } } diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml index 42d3c70f6f..d87015a620 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml @@ -34,13 +34,6 @@ HorizontalAlignment="Center" Visible="false"/> - - - - diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs index 2fb3b0389e..3872fa50cc 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs @@ -4,7 +4,6 @@ using System.Numerics; using Content.Client.Pinpointer.UI; using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; -using Content.Shared.Medical.CrewMonitoring; using Content.Shared.Medical.SuitSensor; using Content.Shared.StatusIcon; using Robust.Client.AutoGenerated; @@ -26,14 +25,12 @@ public sealed partial class CrewMonitoringWindow : FancyWindow { [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly ILocalizationManager _loc = default!; // Sunrise - Added private readonly SharedTransformSystem _transformSystem; private readonly SpriteSystem _spriteSystem; private NetEntity? _trackedEntity; private bool _tryToScrollToListFocus; private Texture? _blipTexture; - private CrewMonitoringBoundUserInterface? _boundUserInterface; public CrewMonitoringWindow() { @@ -44,11 +41,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow _spriteSystem = _entManager.System(); NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap; - - // Sunrise - Start: Alert - CorpseAlertToggle.OnToggled += OnCorpseAlertTogglePressed; - UpdateCorpseAlertToggle(false); - // Sunrise - End: Alert } public void Set(string stationName, EntityUid? mapUid) @@ -66,11 +58,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow NavMap.ForceNavMapUpdate(); } - public void SetBoundUserInterface(CrewMonitoringBoundUserInterface boundUserInterface) - { - _boundUserInterface = boundUserInterface; - } - protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); @@ -460,34 +447,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow NavMap.TrackedEntities.Clear(); NavMap.LocalizedNames.Clear(); } - - // Sunrise - Start: Alert - public void UpdateCorpseAlertToggle(bool enabled) - { - CorpseAlertToggle.Pressed = enabled; - - CorpseAlertToggle.Text = _loc.GetString(enabled ? "crew-monitoring-corpse-alert-on" : "crew-monitoring-corpse-alert-off"); - CorpseAlertToggle.Label.FontColorOverride = enabled ? Color.LimeGreen : Color.Red; - - CorpseAlertToggle.Disabled = false; - } - - private void OnCorpseAlertTogglePressed(BaseButton.ButtonToggledEventArgs args) - { - CorpseAlertToggle.Pressed = args.Pressed; - CorpseAlertToggle.Disabled = true; - _boundUserInterface?.SendMessage(new CrewMonitoringToggleCorpseAlertMessage()); - } - // Sunrise - End: Alert - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - _boundUserInterface = null; - } - } } public sealed class CrewMonitoringButton : Button diff --git a/Content.Client/_Starlight/Medical/CrewMonitoring/Brigmedic/BrigmedicCrewMonitoringBoundUserInterface.cs b/Content.Client/_Starlight/Medical/CrewMonitoring/Brigmedic/BrigmedicCrewMonitoringBoundUserInterface.cs deleted file mode 100644 index 10a20124b3..0000000000 --- a/Content.Client/_Starlight/Medical/CrewMonitoring/Brigmedic/BrigmedicCrewMonitoringBoundUserInterface.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Content.Shared.Medical.CrewMonitoring; -using Robust.Client.UserInterface; -using System.Linq; -using Content.Shared.Access; -using Content.Shared.Access.Components; -using Content.Shared.Access.Systems; -using Content.Shared.Containers.ItemSlots; -using Robust.Client.UserInterface; -using Robust.Shared.Prototypes; -using static Content.Shared.Access.Components.AccessOverriderComponent; -namespace Content.Client.Medical.CrewMonitoring.Brigmedic; - -public class BrigmedicCrewMonitoringBoundUserInterface : BoundUserInterface -{ - [ViewVariables] - protected CrewMonitoringWindow? _menu; - - public BrigmedicCrewMonitoringBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - _accessOverriderSystem = EntMan.System(); - } - protected readonly SharedAccessOverriderSystem _accessOverriderSystem = default!; - - protected override void Open() - { - base.Open(); - EntityUid? gridUid = null; - var stationName = string.Empty; - - if (EntMan.TryGetComponent(Owner, out var xform)) - { - gridUid = xform.GridUid; - - if (EntMan.TryGetComponent(gridUid, out var metaData)) - { - stationName = metaData.EntityName; - } - } - - _menu = this.CreateWindow(); - _menu.Set(stationName, gridUid); - } - - protected override void UpdateState(BoundUserInterfaceState state) - { - base.UpdateState(state); - switch (state) - { - case CrewMonitoringState st: - EntMan.TryGetComponent(Owner, out var xform); - var securityDepartmentSensors = st.Sensors - .Where(sensor => sensor.JobDepartments.Contains("Security")) - .ToList(); - _menu?.ShowSensors(securityDepartmentSensors, Owner, xform?.Coordinates); - break; - } - } -} diff --git a/Content.Client/_Sunrise/Medical/CrewMonitoring/BSOCrewMonitoringBoundUserInterface.cs b/Content.Client/_Sunrise/Medical/CrewMonitoring/BSOCrewMonitoringBoundUserInterface.cs deleted file mode 100644 index eb0d13cf64..0000000000 --- a/Content.Client/_Sunrise/Medical/CrewMonitoring/BSOCrewMonitoringBoundUserInterface.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Content.Shared.Medical.CrewMonitoring; -using Robust.Client.UserInterface; -using System.Linq; -using Content.Shared.Access; -using Content.Shared.Access.Components; -using Content.Shared.Access.Systems; -using Content.Shared.Containers.ItemSlots; -using Robust.Client.UserInterface; -using Robust.Shared.Prototypes; -using static Content.Shared.Access.Components.AccessOverriderComponent; -using Content.Shared.Implants.Components; -namespace Content.Client.Medical.CrewMonitoring.BSO; - -public class BSOCrewMonitoringBoundUserInterface : BoundUserInterface -{ - [ViewVariables] - protected CrewMonitoringWindow? _menu; - - public BSOCrewMonitoringBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - _accessOverriderSystem = EntMan.System(); - } - protected readonly SharedAccessOverriderSystem _accessOverriderSystem = default!; - - protected override void Open() - { - base.Open(); - EntityUid? gridUid = null; - var stationName = string.Empty; - - if (EntMan.TryGetComponent(Owner, out var xform)) - { - gridUid = xform.GridUid; - - if (EntMan.TryGetComponent(gridUid, out var metaData)) - { - stationName = metaData.EntityName; - } - } - - _menu = this.CreateWindow(); - _menu.Set(stationName, gridUid); - } - - protected override void UpdateState(BoundUserInterfaceState state) - { - base.UpdateState(state); - switch (state) - { - case CrewMonitoringState st: - EntMan.TryGetComponent(Owner, out var xform); - var commandDepartmentSensors = st.Sensors - .Where(sensor => sensor.JobDepartments.Contains("Command")) - .ToList(); - //also ALWAYS include the trackers - //this is jank as there isnt a direct indication of a tracker in the suit sensor status - //so we need to check the component directly - foreach (var sensor in st.Sensors) - { - //get the client entity - var clientEntity = EntMan.GetEntity(sensor.SuitSensorUid); - if (EntMan.TryGetComponent(clientEntity, out var suitSensor)) - { - commandDepartmentSensors.Add(sensor); - } - } - //remove duplicates - commandDepartmentSensors = commandDepartmentSensors.Distinct().ToList(); - _menu?.ShowSensors(commandDepartmentSensors, Owner, xform?.Coordinates); - break; - } - } -} diff --git a/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringBoundUserInterface.cs b/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringBoundUserInterface.cs new file mode 100644 index 0000000000..14552f076a --- /dev/null +++ b/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringBoundUserInterface.cs @@ -0,0 +1,46 @@ +using Content.Shared.Medical.CrewMonitoring; +using Robust.Client.UserInterface; + +namespace Content.Client._Sunrise.Medical.CrewMonitoring; + +public class SunriseCrewMonitoringBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + protected SunriseCrewMonitoringWindow? _menu; + + public SunriseCrewMonitoringBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + EntityUid? gridUid = null; + var stationName = string.Empty; + + if (EntMan.TryGetComponent(Owner, out var xform)) + { + gridUid = xform.GridUid; + + if (EntMan.TryGetComponent(gridUid, out var metaData)) + stationName = metaData.EntityName; + } + + _menu = this.CreateWindow(); + _menu.SetBoundUserInterface(this); + _menu.Set(stationName, gridUid); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not CrewMonitoringState st) + return; + + EntMan.TryGetComponent(Owner, out var xform); + _menu?.ShowSensors(st.Sensors, Owner, xform?.Coordinates); + _menu?.UpdateCorpseAlertToggle(st.CorpseAlertEnabled); + } +} diff --git a/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringWindow.xaml b/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringWindow.xaml new file mode 100644 index 0000000000..352cd3b3fc --- /dev/null +++ b/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringWindow.xaml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringWindow.xaml.cs b/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringWindow.xaml.cs new file mode 100644 index 0000000000..8c62b6a50c --- /dev/null +++ b/Content.Client/_Sunrise/Medical/CrewMonitoring/SunriseCrewMonitoringWindow.xaml.cs @@ -0,0 +1,504 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Numerics; +using Content.Client.Pinpointer.UI; +using Content.Client.Stylesheets; +using Content.Client.UserInterface.Controls; +using Content.Shared.Medical.CrewMonitoring; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.StatusIcon; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using static Robust.Client.UserInterface.Controls.BoxContainer; + +namespace Content.Client._Sunrise.Medical.CrewMonitoring; + +[GenerateTypedNameReferences] +public sealed partial class SunriseCrewMonitoringWindow : FancyWindow +{ + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + private readonly SharedTransformSystem _transformSystem; + private readonly SpriteSystem _spriteSystem; + + private NetEntity? _trackedEntity; + private bool _tryToScrollToListFocus; + private Texture? _blipTexture; + private BoundUserInterface? _boundUserInterface; + private TimeSpan _lastStateUpdateTime; + private bool _showingEmptyState; + private int _emptyStateUpdateCount; + private bool _serverFound; + + private const float ServerUpdateTimeoutSeconds = 7f; + + public SunriseCrewMonitoringWindow() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _transformSystem = _entManager.System(); + _spriteSystem = _entManager.System(); + + NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap; + + CorpseAlertToggle.OnToggled += OnCorpseAlertTogglePressed; + UpdateCorpseAlertToggle(false); + } + + public void Set(string stationName, EntityUid? mapUid) + { + _blipTexture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png"))); + + if (_entManager.TryGetComponent(mapUid, out var xform)) + NavMap.MapUid = xform.GridUid; + else + NavMap.Visible = false; + + StationName.AddStyleClass("LabelBig"); + StationName.Text = stationName; + NavMap.ForceNavMapUpdate(); + } + + public void SetBoundUserInterface(BoundUserInterface boundUserInterface) + { + _boundUserInterface = boundUserInterface; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + if (_tryToScrollToListFocus) + TryToScrollToFocus(); + + if (_showingEmptyState) + UpdateNoServerLabelText(); + } + + public void ShowSensors(List sensors, EntityUid monitor, EntityCoordinates? monitorCoords) + { + ClearOutDatedData(); + _lastStateUpdateTime = _gameTiming.CurTime; + + if (sensors.Count == 0) + { + NoServerLabel.Visible = true; + _showingEmptyState = true; + + if (!_serverFound && ++_emptyStateUpdateCount >= 2) + _serverFound = true; + + UpdateNoServerLabelText(); + return; + } + + NoServerLabel.Visible = false; + _showingEmptyState = false; + _serverFound = true; + _emptyStateUpdateCount = 0; + + Dictionary uniqueSensorsMap = new(); + foreach (var sensor in sensors) + { + if (uniqueSensorsMap.TryGetValue(sensor.OwnerUid, out var existingSensor)) + { + if (existingSensor.Coordinates != null && sensor.Coordinates == null) + continue; + + if (existingSensor.DamagePercentage != null && sensor.DamagePercentage == null) + continue; + } + + uniqueSensorsMap[sensor.OwnerUid] = sensor; + } + + var uniqueSensors = uniqueSensorsMap.Values.ToList(); + + var orderedSensors = uniqueSensors.OrderBy(j => j.Job).ThenBy(n => n.Name); + var assignedSensors = new HashSet(); + var departments = uniqueSensors.SelectMany(d => d.JobDepartments).Distinct().OrderBy(n => n); + + foreach (var department in departments) + { + var departmentSensors = orderedSensors.Where(d => d.JobDepartments.Contains(department)); + + if (departmentSensors == null || !departmentSensors.Any()) + continue; + + foreach (var sensor in departmentSensors) + assignedSensors.Add(sensor); + + if (SensorsTable.ChildCount > 0) + { + var spacer = new Control() + { + SetHeight = 20, + }; + + SensorsTable.AddChild(spacer); + } + + var deparmentLabel = new RichTextLabel() + { + Margin = new Thickness(10, 0), + HorizontalExpand = true, + }; + + deparmentLabel.SetMessage(department); + deparmentLabel.StyleClasses.Add("font-large"); + + SensorsTable.AddChild(deparmentLabel); + + PopulateDepartmentList(departmentSensors); + } + + var remainingSensors = orderedSensors.Except(assignedSensors); + + if (remainingSensors.Any()) + { + var spacer = new Control() + { + SetHeight = 20, + }; + + SensorsTable.AddChild(spacer); + + var deparmentLabel = new RichTextLabel() + { + Margin = new Thickness(10, 0), + HorizontalExpand = true, + }; + + deparmentLabel.SetMessage(Loc.GetString("crew-monitoring-ui-no-department-label")); + + SensorsTable.AddChild(deparmentLabel); + + PopulateDepartmentList(remainingSensors); + } + + if (monitorCoords != null && _blipTexture != null) + { + NavMap.TrackedEntities[_entManager.GetNetEntity(monitor)] = new NavMapBlip(monitorCoords.Value, _blipTexture, Color.Cyan, true, false); + } + } + + private void UpdateNoServerLabelText() + { + var timedOut = (_gameTiming.CurTime - _lastStateUpdateTime) > TimeSpan.FromSeconds(ServerUpdateTimeoutSeconds); + if (timedOut) + { + _serverFound = false; + _emptyStateUpdateCount = 0; + } + + NoServerLabel.Text = Loc.GetString(_serverFound && !timedOut + ? "crew-monitoring-ui-no-sensors-label" + : "crew-monitoring-ui-no-server-label"); + } + + public void ClearSensors(bool showNoServerLabel) + { + ClearOutDatedData(); + NoServerLabel.Visible = showNoServerLabel; + _showingEmptyState = showNoServerLabel; + + if (showNoServerLabel) + { + _serverFound = false; + _emptyStateUpdateCount = 0; + _lastStateUpdateTime = TimeSpan.Zero; + UpdateNoServerLabelText(); + } + } + + private void PopulateDepartmentList(IEnumerable departmentSensors) + { + 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); + + NavMap.LocalizedNames.TryAdd(sensor.SuitSensorUid, sensor.Name + ", " + sensor.Job); + + var sensorButton = new CrewMonitoringButton() + { + SuitSensorUid = sensor.SuitSensorUid, + Coordinates = coordinates, + Disabled = (coordinates == null), + HorizontalExpand = true, + }; + + if (sensor.SuitSensorUid == _trackedEntity) + sensorButton.AddStyleClass(StyleClass.Positive); + + SensorsTable.AddChild(sensorButton); + + var mainContainer = new BoxContainer() + { + Orientation = LayoutOrientation.Horizontal, + HorizontalExpand = true, + }; + + sensorButton.AddChild(mainContainer); + + var statusContainer = new BoxContainer() + { + SizeFlagsStretchRatio = 1.25f, + Orientation = LayoutOrientation.Horizontal, + HorizontalExpand = true, + }; + + mainContainer.AddChild(statusContainer); + + var suitCoordsIndicator = new TextureRect() + { + Texture = _blipTexture, + TextureScale = new Vector2(0.25f, 0.25f), + Modulate = coordinates != null ? Color.LimeGreen : Color.DarkRed, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + }; + + statusContainer.AddChild(suitCoordsIndicator); + + var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "alive"); + + if (!sensor.IsAlive) + specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead"); + + else if (sensor.DamagePercentage != null) + { + var index = MathF.Round(4f * sensor.DamagePercentage.Value); + + if (index >= 5) + specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical"); + + else + specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "health" + index); + } + + var statusIcon = new AnimatedTextureRect + { + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + Margin = new Thickness(0, 1, 3, 0), + }; + + statusIcon.SetFromSpriteSpecifier(specifier); + statusIcon.DisplayRect.TextureScale = new Vector2(2f, 2f); + + statusContainer.AddChild(statusIcon); + + var nameLabel = new Label() + { + Text = sensor.Name, + HorizontalExpand = true, + ClipText = true, + }; + + statusContainer.AddChild(nameLabel); + + var jobContainer = new BoxContainer() + { + Orientation = LayoutOrientation.Horizontal, + HorizontalExpand = true, + }; + + mainContainer.AddChild(jobContainer); + + if (_prototypeManager.TryIndex(sensor.JobIcon, out var proto)) + { + var jobIcon = new TextureRect() + { + TextureScale = new Vector2(2f, 2f), + VerticalAlignment = VAlignment.Center, + Texture = _spriteSystem.Frame0(proto.Icon), + Margin = new Thickness(5, 0, 5, 0), + }; + + jobContainer.AddChild(jobIcon); + } + + var jobLabel = new Label() + { + Text = sensor.Job, + HorizontalExpand = true, + ClipText = true, + }; + + jobContainer.AddChild(jobLabel); + + if (coordinates != null && NavMap.Visible && _blipTexture != null) + { + NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid, + new NavMapBlip + (CoordinatesToLocal(coordinates.Value), + _blipTexture, + (_trackedEntity == null || sensor.SuitSensorUid == _trackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray, + sensor.SuitSensorUid == _trackedEntity)); + + NavMap.Focus = _trackedEntity; + + sensorButton.OnButtonUp += args => + { + var prevTrackedEntity = _trackedEntity; + + if (_trackedEntity == sensor.SuitSensorUid) + _trackedEntity = null; + + else + { + _trackedEntity = sensor.SuitSensorUid; + NavMap.CenterToCoordinates(coordinates.Value); + } + + NavMap.Focus = _trackedEntity; + + UpdateSensorsTable(_trackedEntity, prevTrackedEntity); + }; + } + } + } + + private void SetTrackedEntityFromNavMap(NetEntity? netEntity) + { + var prevTrackedEntity = _trackedEntity; + _trackedEntity = netEntity; + + if (_trackedEntity == prevTrackedEntity) + prevTrackedEntity = null; + + NavMap.Focus = _trackedEntity; + _tryToScrollToListFocus = true; + + UpdateSensorsTable(_trackedEntity, prevTrackedEntity); + } + + private void UpdateSensorsTable(NetEntity? currTrackedEntity, NetEntity? prevTrackedEntity) + { + foreach (var sensor in SensorsTable.Children) + { + if (sensor is not CrewMonitoringButton) + continue; + + var castSensor = (CrewMonitoringButton)sensor; + + if (castSensor.SuitSensorUid == prevTrackedEntity) + castSensor.RemoveStyleClass(StyleClass.Positive); + else if (castSensor.SuitSensorUid == currTrackedEntity) + castSensor.AddStyleClass(StyleClass.Positive); + + if (castSensor?.Coordinates == null) + continue; + + if (NavMap.TrackedEntities.TryGetValue(castSensor.SuitSensorUid, out var data)) + { + data = new NavMapBlip + (CoordinatesToLocal(data.Coordinates), + data.Texture, + (currTrackedEntity == null || castSensor.SuitSensorUid == currTrackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray, + castSensor.SuitSensorUid == currTrackedEntity); + + NavMap.TrackedEntities[castSensor.SuitSensorUid] = data; + } + } + } + + private void TryToScrollToFocus() + { + if (!_tryToScrollToListFocus) + return; + + if (TryGetNextScrollPosition(out float? nextScrollPosition)) + { + SensorScroller.VScrollTarget = nextScrollPosition.Value; + + if (MathHelper.CloseToPercent(SensorScroller.VScroll, SensorScroller.VScrollTarget)) + { + _tryToScrollToListFocus = false; + return; + } + } + } + + private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition) + { + nextScrollPosition = 0; + + foreach (var sensor in SensorsTable.Children) + { + if (sensor is CrewMonitoringButton && + ((CrewMonitoringButton)sensor).SuitSensorUid == _trackedEntity) + return true; + + nextScrollPosition += sensor.Height; + } + + nextScrollPosition = null; + return false; + } + + private EntityCoordinates CoordinatesToLocal(EntityCoordinates refCoords) + { + if (NavMap.MapUid != null) + return _transformSystem.WithEntityId(refCoords, (EntityUid)NavMap.MapUid); + else + return refCoords; + } + + private void ClearOutDatedData() + { + SensorsTable.RemoveAllChildren(); + NavMap.TrackedCoordinates.Clear(); + NavMap.TrackedEntities.Clear(); + NavMap.LocalizedNames.Clear(); + } + + public void UpdateCorpseAlertToggle(bool enabled) + { + CorpseAlertToggle.Pressed = enabled; + + CorpseAlertToggle.Text = _loc.GetString(enabled ? "crew-monitoring-corpse-alert-on" : "crew-monitoring-corpse-alert-off"); + CorpseAlertToggle.Label.FontColorOverride = enabled ? Color.LimeGreen : Color.Red; + + CorpseAlertToggle.Disabled = false; + } + + private void OnCorpseAlertTogglePressed(BaseButton.ButtonToggledEventArgs args) + { + CorpseAlertToggle.Pressed = args.Pressed; + CorpseAlertToggle.Disabled = true; + _boundUserInterface?.SendMessage(new CrewMonitoringToggleCorpseAlertMessage()); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + _boundUserInterface = null; + } + } +} + +public sealed class CrewMonitoringButton : Button +{ + public int IndexInTable; + public NetEntity SuitSensorUid; + public EntityCoordinates? Coordinates; +} diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs index c1cb2577c8..7139c78924 100644 --- a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs +++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs @@ -82,7 +82,10 @@ public sealed partial class CrewMonitoringConsoleSystem : EntitySystem // Sunris // Update all sensors info var allSensors = component.ConnectedSensors.Values.ToList(); + // Sunrise - Start + ApplyFilter(uid, ref allSensors); + var corpseAlertEnabled = TryComp(uid, out CrewMonitoringCorpseAlertComponent? alert) && alert.DoCorpseAlert; _uiSystem.SetUiState(uid, CrewMonitoringUIKey.Key, new CrewMonitoringState(allSensors, corpseAlertEnabled)); // Sunrise - End diff --git a/Content.Server/_Sunrise/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.Filter.cs b/Content.Server/_Sunrise/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.Filter.cs new file mode 100644 index 0000000000..7641a5cb9b --- /dev/null +++ b/Content.Server/_Sunrise/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.Filter.cs @@ -0,0 +1,84 @@ +using Content.Shared.Implants.Components; +using Content.Shared.Medical.CrewMonitoring; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.Roles; +using Robust.Shared.Prototypes; + +namespace Content.Server.Medical.CrewMonitoring; + +public sealed partial class CrewMonitoringConsoleSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + private void ApplyFilter(EntityUid uid, ref List sensors) + { + if (!TryComp(uid, out CrewMonitoringFilterComponent? filter)) + return; + + var showOnlyWoundedOrDead = filter.OnlyShowWoundedOrDead; + var filterByDepartment = filter.AllowedDepartmentIds.Count > 0; + + if (!showOnlyWoundedOrDead && !filterByDepartment) + return; + + HashSet? allowedDepartmentNames = null; + if (filterByDepartment) + allowedDepartmentNames = BuildAllowedDepartmentNameSet(filter.AllowedDepartmentIds); + + var includeTrackers = filter.IncludeTrackers; + var filteredSensors = new List(sensors.Count); + foreach (var sensor in sensors) + { + if (showOnlyWoundedOrDead && !IsWoundedOrDead(sensor)) + continue; + + if (allowedDepartmentNames != null) + { + if (!IsInAllowedDepartments(sensor, allowedDepartmentNames, includeTrackers)) + continue; + } + + filteredSensors.Add(sensor); + } + + sensors = filteredSensors; + } + + private HashSet BuildAllowedDepartmentNameSet(List departmentIds) + { + var allowedDepartments = new HashSet(); + + foreach (var departmentId in departmentIds) + { + if (_prototypeManager.TryIndex(departmentId, out var department)) + allowedDepartments.Add(Loc.GetString(department.Name)); + else + allowedDepartments.Add(departmentId); + } + + return allowedDepartments; + } + + private bool IsWoundedOrDead(SuitSensorStatus sensor) + { + if (!sensor.IsAlive) + return true; + + return sensor.DamagePercentage is >= CriticalDamagePercentage; + } + + private bool IsInAllowedDepartments(SuitSensorStatus sensor, HashSet allowedDepartmentNames, bool includeTrackers) + { + foreach (var department in sensor.JobDepartments) + { + if (allowedDepartmentNames.Contains(department)) + return true; + } + + if (!includeTrackers) + return false; + + var sensorEntity = GetEntity(sensor.SuitSensorUid); + return HasComp(sensorEntity); + } +} diff --git a/Content.Shared/_Sunrise/Medical/CrewMonitoring/CrewMonitoringFilterComponent.cs b/Content.Shared/_Sunrise/Medical/CrewMonitoring/CrewMonitoringFilterComponent.cs new file mode 100644 index 0000000000..879846c436 --- /dev/null +++ b/Content.Shared/_Sunrise/Medical/CrewMonitoring/CrewMonitoringFilterComponent.cs @@ -0,0 +1,22 @@ +namespace Content.Shared.Medical.CrewMonitoring; + +[RegisterComponent] +public sealed partial class CrewMonitoringFilterComponent : Component +{ + /// + /// Разрешенные отделы. Если пустое все доступны + /// + [DataField] + public List AllowedDepartmentIds = new(); + /// + /// Будут ли отображаться по трекерам + /// + [DataField] + public bool IncludeTrackers; + /// + /// Показывать ли мертвых, в крите, в ужасном + /// + [DataField] + public bool OnlyShowWoundedOrDead; +} + diff --git a/Resources/Locale/en-US/_prototypes/_sunrise/entities/objects/specific/medical/handheld_emergency_crew_monitor.ftl b/Resources/Locale/en-US/_prototypes/_sunrise/entities/objects/specific/medical/handheld_emergency_crew_monitor.ftl new file mode 100644 index 0000000000..f0dfba45b3 --- /dev/null +++ b/Resources/Locale/en-US/_prototypes/_sunrise/entities/objects/specific/medical/handheld_emergency_crew_monitor.ftl @@ -0,0 +1,5 @@ +ent-HandheldEmergencyCrewMonitor = PulseGuard™ XR + .desc = A hand-held crew monitor displaying the status of suit sensors of injured crew. +ent-HandheldEmergencyCrewMonitorEmpty = { ent-HandheldEmergencyCrewMonitor } + .suffix = Empty + .desc = { ent-HandheldEmergencyCrewMonitor.desc } diff --git a/Resources/Locale/en-US/_strings/_sunrise/medical/crew-monitoring.ftl b/Resources/Locale/en-US/_strings/_sunrise/medical/crew-monitoring.ftl index 110d10a78b..46fa268cc2 100644 --- a/Resources/Locale/en-US/_strings/_sunrise/medical/crew-monitoring.ftl +++ b/Resources/Locale/en-US/_strings/_sunrise/medical/crew-monitoring.ftl @@ -1,6 +1,7 @@ crew-monitoring-corpse-alert = Alert signal: crew-monitoring-corpse-alert-on = ON crew-monitoring-corpse-alert-off = OFF +crew-monitoring-ui-no-sensors-label = Server found, no matching sensors item-toggle-deactivate-alert = Deactivate alert item-toggle-activate-alert = Activate alert diff --git a/Resources/Locale/ru-RU/_prototypes/_sunrise/entities/objects/specific/medical/handheld_bso_crew_monitor.ftl b/Resources/Locale/ru-RU/_prototypes/_sunrise/entities/objects/specific/medical/handheld_bso_crew_monitor.ftl index efc655ba06..72dba66c53 100644 --- a/Resources/Locale/ru-RU/_prototypes/_sunrise/entities/objects/specific/medical/handheld_bso_crew_monitor.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_sunrise/entities/objects/specific/medical/handheld_bso_crew_monitor.ftl @@ -1,5 +1,5 @@ -ent-HandheldBSOCrewMonitor = CommandFriend™ X-02 - .desc = Не отслеживает уровень компетентности командного состава. +ent-HandheldBSOCrewMonitor = Команд-Друг™ X-02 + .desc = Не отслеживает уровень компетентности командного состава. Показывает только командный состав. ent-HandheldBSOCrewMonitorEmpty = { ent-HandheldBSOCrewMonitor } .suffix = Пустой .desc = { ent-HandheldBSOCrewMonitor.desc } diff --git a/Resources/Locale/ru-RU/_prototypes/_sunrise/entities/objects/specific/medical/handheld_emergency_crew_monitor.ftl b/Resources/Locale/ru-RU/_prototypes/_sunrise/entities/objects/specific/medical/handheld_emergency_crew_monitor.ftl new file mode 100644 index 0000000000..a3da6521e8 --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/_sunrise/entities/objects/specific/medical/handheld_emergency_crew_monitor.ftl @@ -0,0 +1,5 @@ +ent-HandheldEmergencyCrewMonitor = Пульс-Гард™ XR + .desc = Ручной монитор экипажа, отображающий состояние датчиков скафандра раненых членов экипажа. +ent-HandheldEmergencyCrewMonitorEmpty = { ent-HandheldEmergencyCrewMonitor } + .suffix = Пустой + .desc = { ent-HandheldEmergencyCrewMonitor.desc } diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/medical/crew-monitoring.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/medical/crew-monitoring.ftl index d66b69a36c..728ce25a8c 100644 --- a/Resources/Locale/ru-RU/_strings/_sunrise/medical/crew-monitoring.ftl +++ b/Resources/Locale/ru-RU/_strings/_sunrise/medical/crew-monitoring.ftl @@ -1,6 +1,8 @@ crew-monitoring-corpse-alert = Сигнал тревоги: crew-monitoring-corpse-alert-on = ВКЛ crew-monitoring-corpse-alert-off = ВЫКЛ +crew-monitoring-ui-no-server-label = Сервер не найден +crew-monitoring-ui-no-sensors-label = Сервер найден, подходящих датчиков нет item-toggle-deactivate-alert = Деактивировать тревогу item-toggle-activate-alert = Активировать тревогу diff --git a/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml b/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml index 4dcfdef6b5..a018c27092 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml @@ -26,7 +26,7 @@ storage: back: - EmergencyRollerBedSpawnFolded - - HandheldCrewMonitor # Sunrise-Edit + - HandheldEmergencyCrewMonitor # Sunrise - edit - type: chameleonOutfit id: ParamedicChameleonOutfit diff --git a/Resources/Prototypes/_Starlight/Entities/Objects/Specific/Medical/handheld_brigmedic_crew_monitor.yml b/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_brigmedic_crew_monitor.yml similarity index 82% rename from Resources/Prototypes/_Starlight/Entities/Objects/Specific/Medical/handheld_brigmedic_crew_monitor.yml rename to Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_brigmedic_crew_monitor.yml index 437465a38d..24689c19cc 100644 --- a/Resources/Prototypes/_Starlight/Entities/Objects/Specific/Medical/handheld_brigmedic_crew_monitor.yml +++ b/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_brigmedic_crew_monitor.yml @@ -8,15 +8,18 @@ tags: - BrigmedicBeltEquip - type: Sprite - sprite: _Starlight/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi + sprite: _Sunrise/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi state: scanner - type: ActivatableUI key: enum.CrewMonitoringUIKey.Key - type: UserInterface interfaces: enum.CrewMonitoringUIKey.Key: - type: BrigmedicCrewMonitoringBoundUserInterface + type: SunriseCrewMonitoringBoundUserInterface - type: CrewMonitoringConsole + - type: CrewMonitoringFilter + allowedDepartmentIds: + - Security - type: DeviceNetwork deviceNetId: Wireless receiveFrequencyId: CrewMonitor diff --git a/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_bso_crew_monitor.yml b/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_bso_crew_monitor.yml index 2000f1d45b..4e0d367a14 100644 --- a/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_bso_crew_monitor.yml +++ b/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_bso_crew_monitor.yml @@ -12,8 +12,12 @@ - type: UserInterface interfaces: enum.CrewMonitoringUIKey.Key: - type: BSOCrewMonitoringBoundUserInterface + type: SunriseCrewMonitoringBoundUserInterface - type: CrewMonitoringConsole + - type: CrewMonitoringFilter + allowedDepartmentIds: + - Command + includeTrackers: true - type: DeviceNetwork deviceNetId: Wireless receiveFrequencyId: CrewMonitor diff --git a/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_emergency_crew_monitor.yml b/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_emergency_crew_monitor.yml new file mode 100644 index 0000000000..43a4d5359c --- /dev/null +++ b/Resources/Prototypes/_Sunrise/Entities/Objects/Specific/Medical/handheld_emergency_crew_monitor.yml @@ -0,0 +1,39 @@ +- type: entity + name: PulseGuard™ XR + categories: [ DoNotMap ] + parent: [ BaseHandheldComputer ] + id: HandheldEmergencyCrewMonitor + description: A hand-held crew monitor displaying the status of suit sensors of injured crew. + components: + - type: Item + heldPrefix: scanner + - type: Sprite + sprite: _Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi + state: scanner + - type: ActivatableUI + key: enum.CrewMonitoringUIKey.Key + - type: UserInterface + interfaces: + enum.CrewMonitoringUIKey.Key: + type: SunriseCrewMonitoringBoundUserInterface + - type: CrewMonitoringConsole + - type: CrewMonitoringFilter + onlyShowWoundedOrDead: true + - type: DeviceNetwork + deviceNetId: Wireless + receiveFrequencyId: CrewMonitor + - type: WirelessNetworkConnection + range: 500 + - type: StationLimitedNetwork + - type: StaticPrice + price: 500 + +- type: entity + id: HandheldEmergencyCrewMonitorEmpty + parent: HandheldEmergencyCrewMonitor + suffix: Empty + components: + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default diff --git a/Resources/Textures/_Starlight/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/icon.png b/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/icon.png similarity index 100% rename from Resources/Textures/_Starlight/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/icon.png rename to Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/icon.png diff --git a/Resources/Textures/_Starlight/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/meta.json b/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/meta.json similarity index 100% rename from Resources/Textures/_Starlight/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/meta.json rename to Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/meta.json diff --git a/Resources/Textures/_Starlight/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/scanner.png b/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/scanner.png similarity index 100% rename from Resources/Textures/_Starlight/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/scanner.png rename to Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_brigmedic_crewmonitor.rsi/scanner.png diff --git a/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/icon.png b/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..47a4cbc2b94c2aa475027032bce894df878f9b40 GIT binary patch literal 553 zcmV+^0@nSBP)DTO~fMRH(N!~6R#Q`ec-~Fm;ful!J!6L z%v%88HU;o)vjR+@l>%Yz0+9mv=36PSSUgrrumZqKO+=uT0!W0tXAh+!RUng_B3=>p zeY#{&)_V91VD(rzdk_I0^CB@zdUXqixe0;AJPCooi^#bWoP*_3iSSp%U(f4zHOPar*>~ck?LY-M5bx`6J-{`A+oK);F3ceeEJ#4WSrH9oW(d`f*H9(B rTwO$85jhjJqb795kK3^Vf1Bm7qg>CTffi; zRu!=xXhXay*G*oZxGzFWy2PMz$@+_g!Eg>ZfSJDkN_|OH zt%B~=@SAb7jIWvYYQOv1xkI>qR{N9Uqmer;_X_;nA@LK<6d!$VM0yy{Vp%Fr(f9()KtSYB7EhNVr`kPnJV7MS#|5y#7TQU zMfCP`TW__odXf@r$oApe7meUrgdeGI?UC)T+xxHA%zZKEp`U&$Rg8l~rkvYp@Z!~>H4p#nD-sM@ zw&E7!74oSIS?>uF|VlBinAAQJpN^_H{k-KOSFjZ zt_>^J>0PL*__$QR{>NJ*tM|WKR`;13y?wmmyX+eCc`u|G=2v_#jb2}$EgH+AGpFaR zQs;)UE8#!pyxaX{eeDKSMj-`8p&zeV>=+NRyi&-^+_43it{6OB{an^LB{Ts5J9`wx literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/scanner-inhand-right.png b/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/scanner-inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..b70fe157e5270aa808ced470f7184087673b4783 GIT binary patch literal 630 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV6ydeaSW+od^^k5o7qvIOJTKX#)6uLeo>!pO%8y4%mKfm-q^XJ10s<h*u>W?o&-FlhlV%U3apIa0sHOn&+EZ#Ub( zd~xen@41GKTV?-o9y)((eVD?N%ocR-qlE^D`(VAmfr-I;o!yP&-p3gyf3N;0U)%bT z`*`6t{q?dklciI)d^~LN?)TFr*Y|C_yJq#P5{dRj%RFAKoiTahlbPI#QqqNgPb?F^ zGdcOiPsO<34Y|>5pC|jT`o(aCP3mBw(JB`OjhTk2#xJVBUdW$zBDgLd;7tZ?StzUuZiNVv=&t;ucLK6VhiW}4b literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/scanner.png b/Resources/Textures/_Sunrise/Objects/Specific/Medical/handheld_emergency_crewmonitor.rsi/scanner.png new file mode 100644 index 0000000000000000000000000000000000000000..454ffd7547c2727f47fab181398e1af34af2543f GIT binary patch literal 734 zcmV<40wMj0P)>Bhrup>$CGf3`eLwwCIsSEamYzJ#)8qUz-7Zx>3I5XYVL-!U zW2*gWa@=a}YZIk(Na7of1bAC#w=(PBpI-zLZYpqI*L6XHH2#B|oJsa~4fUw5xxF7$ zm?=}5gaYd&$2Z#MdDC#&o_g@D31A0ySAQxe(9;oDBQ5{B3Z zj1Tmnk8v&xlE!~%Yvik3j`|CgnEGkucdU&D^r{40>u4cYAVv%DXC|1s#VFRrhE(sZRkl99iQ!Rfp1N~ z-r5(H0UM=O- z&P6fdBl^H+=^7uYuigT)ya_@V<6ICRjc*6;;uJNr1U}q)nunaIYA+03X^S_Zy=v Q-~a#s07*qoM6N<$f;|IDmH+?% literal 0 HcmV?d00001