From 91a70bdaacf5d038173f659bf2517cf3322d8239 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 9 Apr 2022 13:50:59 +1200 Subject: [PATCH] Add directional icons to crew monitors (#7404) --- .../CrewMonitoringBoundUserInterface.cs | 4 +- .../CrewMonitoring/CrewMonitoringWindow.xaml | 2 +- .../CrewMonitoringWindow.xaml.cs | 66 +++++++++++------ Content.Client/Stylesheets/StyleNano.cs | 15 ++++ .../UserInterface/Controls/DirectionIcon.cs | 70 ++++++++++++++++++ .../CrewMonitoringConsoleComponent.cs | 19 +++-- .../CrewMonitoringConsoleSystem.cs | 22 +++++- .../CrewMonitoring/CrewMonitoringShared.cs | 15 ++-- .../Entities/Mobs/Player/admin_ghost.yml | 2 + .../Textures/Interface/VerbIcons/dot.svg | 48 ++++++++++++ .../Interface/VerbIcons/dot.svg.192dpi.png | Bin 0 -> 570 bytes .../VerbIcons/dot.svg.192dpi.png.yml | 3 + 12 files changed, 226 insertions(+), 40 deletions(-) create mode 100644 Content.Client/UserInterface/Controls/DirectionIcon.cs create mode 100644 Resources/Textures/Interface/VerbIcons/dot.svg create mode 100644 Resources/Textures/Interface/VerbIcons/dot.svg.192dpi.png create mode 100644 Resources/Textures/Interface/VerbIcons/dot.svg.192dpi.png.yml diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs index a5ab6371c6..08904e553c 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringBoundUserInterface.cs @@ -1,4 +1,4 @@ -using Content.Shared.Medical.CrewMonitoring; +using Content.Shared.Medical.CrewMonitoring; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; @@ -27,7 +27,7 @@ namespace Content.Client.Medical.CrewMonitoring switch (state) { case CrewMonitoringState st: - _menu?.ShowSensors(st.Sensors); + _menu?.ShowSensors(st.Sensors, st.WorldPosition, st.WorldRotation, st.Snap, st.Precision); break; } } diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml index 06d1f5883a..b9baac26b9 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml @@ -1,6 +1,6 @@  + SetSize="450 400"> _rowsContent = new(); + public static int IconSize = 16; // XAML has a `VSeparationOverride` of 20 for each row. + public CrewMonitoringWindow() { RobustXamlLoader.Load(this); } - public void ShowSensors(List stSensors) + public void ShowSensors(List stSensors, Vector2 worldPosition, Angle worldRotation, bool snap, float precision) { ClearAllSensors(); + // TODO scroll container + // TODO filter by name & occupation + // TODO make each row a xaml-control. Get rid of some of this c# control creation. + // add a row for each sensor foreach (var sensor in stSensors) { @@ -53,28 +60,43 @@ namespace Content.Client.Medical.CrewMonitoring // add users positions // format: (x, y) - string posText; - if (sensor.Coordinates != null) - { - // todo: add locations names (kitchen, bridge, etc) - var pos = sensor.Coordinates.Value.Position; - var x = (int) pos.X; - var y = (int) pos.Y; - posText = $"({x}, {y})"; - } - else - { - posText = Loc.GetString("crew-monitoring-user-interface-no-info"); - } - var posLabel = new Label() - { - Text = posText - }; - SensorsTable.AddChild(posLabel); - _rowsContent.Add(posLabel); + var box = GetPositionBox(sensor.Coordinates, worldPosition, worldRotation, snap, precision); + SensorsTable.AddChild(box); + _rowsContent.Add(box); } } + private BoxContainer GetPositionBox(MapCoordinates? coordinates, Vector2 sensorPosition, Angle sensorRotation, bool snap, float precision) + { + var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal }; + + if (coordinates == null) + { + var dirIcon = new DirectionIcon() + { + SetSize = (IconSize, IconSize), + Margin = new(0, 0, 4, 0) + }; + box.AddChild(dirIcon); + box.AddChild(new Label() { Text = Loc.GetString("crew-monitoring-user-interface-no-info") }); + } + else + { + // todo: add locations names (kitchen, bridge, etc) + var pos = (Vector2i) coordinates.Value.Position; + var relPos = coordinates.Value.Position - sensorPosition; + var dirIcon = new DirectionIcon(relPos, sensorRotation, snap, minDistance: precision) + { + SetSize = (IconSize, IconSize), + Margin = new(0, 0, 4, 0) + }; + box.AddChild(dirIcon); + box.AddChild(new Label() { Text = pos.ToString() }); + } + + return box; + } + private void ClearAllSensors() { foreach (var child in _rowsContent) diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs index 4e29394781..91efcb200f 100644 --- a/Content.Client/Stylesheets/StyleNano.cs +++ b/Content.Client/Stylesheets/StyleNano.cs @@ -457,6 +457,11 @@ namespace Content.Client.Stylesheets var contextMenuExpansionTexture = resCache.GetTexture("/Textures/Interface/VerbIcons/group.svg.192dpi.png"); var verbMenuConfirmationTexture = resCache.GetTexture("/Textures/Interface/VerbIcons/group.svg.192dpi.png"); + // south-facing arrow: + var directionIconArrowTex = resCache.GetTexture("/Textures/Interface/VerbIcons/drop.svg.192dpi.png"); + var directionIconQuestionTex = resCache.GetTexture("/Textures/Interface/VerbIcons/information.svg.192dpi.png"); + var directionIconHereTex = resCache.GetTexture("/Textures/Interface/VerbIcons/dot.svg.192dpi.png"); + Stylesheet = new Stylesheet(BaseRules.Concat(new[] { // Window title. @@ -680,6 +685,16 @@ namespace Content.Client.Stylesheets .Pseudo(ContainerButton.StylePseudoClassDisabled) .Prop(Control.StylePropertyModulateSelf, ExamineButtonColorContextDisabled), + // Direction / arrow icon + Element().Class(DirectionIcon.StyleClassDirectionIconArrow) + .Prop(TextureRect.StylePropertyTexture, directionIconArrowTex), + + Element().Class(DirectionIcon.StyleClassDirectionIconUnknown) + .Prop(TextureRect.StylePropertyTexture, directionIconQuestionTex), + + Element().Class(DirectionIcon.StyleClassDirectionIconHere) + .Prop(TextureRect.StylePropertyTexture, directionIconHereTex), + // Thin buttons (No padding nor vertical margin) Element().Class(StyleClassStorageButton) .Prop(ContainerButton.StylePropertyStyleBox, buttonStorage), diff --git a/Content.Client/UserInterface/Controls/DirectionIcon.cs b/Content.Client/UserInterface/Controls/DirectionIcon.cs new file mode 100644 index 0000000000..16cebcc9a5 --- /dev/null +++ b/Content.Client/UserInterface/Controls/DirectionIcon.cs @@ -0,0 +1,70 @@ +using Robust.Client.Graphics; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.UserInterface.Controls; + +/// +/// Simple control that shows an arrow pointing in some direction. +/// +/// +/// The actual arrow and other icons are defined in the style sheet. +/// +public sealed class DirectionIcon : TextureRect +{ + public static string StyleClassDirectionIconArrow = "direction-icon-arrow"; // south pointing arrow + public static string StyleClassDirectionIconHere = "direction-icon-here"; // "you have reached your destination" + public static string StyleClassDirectionIconUnknown = "direction-icon-unknown"; // unknown direction / error + + private Angle? _rotation; + + public Angle? Rotation + { + get => _rotation; + set + { + _rotation = value; + SetOnlyStyleClass(value == null ? StyleClassDirectionIconUnknown : StyleClassDirectionIconArrow); + } + } + + public DirectionIcon() + { + Stretch = StretchMode.KeepAspectCentered; + SetOnlyStyleClass(StyleClassDirectionIconUnknown); + } + + public DirectionIcon(Direction direction) : this() + { + Rotation = direction.ToAngle(); + } + + /// + /// Creates an icon with an arrow pointing in some direction. + /// + /// The direction + /// The relative angle. This may be the players eye rotation, the grid rotation, or + /// maybe the world rotation of the entity that owns some BUI + /// If true, will snap the nearest cardinal or diagonal direction + /// If the distance is less than this, the arrow icon will be replaced by some other indicator + public DirectionIcon(Vector2 direction, Angle relativeAngle, bool snap, float minDistance = 0.1f) : this() + { + if (direction.EqualsApprox(Vector2.Zero, minDistance)) + { + SetOnlyStyleClass(StyleClassDirectionIconHere); + return; + } + + var rotation = direction.ToWorldAngle() - relativeAngle; + Rotation = snap ? rotation.GetDir().ToAngle() : rotation; + } + + protected override void Draw(DrawingHandleScreen handle) + { + if (_rotation != null) + { + var offset = (-_rotation.Value).RotateVec(Size * UIScale / 2) - Size * UIScale / 2; + handle.SetTransform(Matrix3.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value)); + } + base.Draw(handle); + } +} diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleComponent.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleComponent.cs index 2ecb05c6e3..269e05bea5 100644 --- a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleComponent.cs +++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleComponent.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; using Content.Shared.Medical.SuitSensor; -using Robust.Shared.Analyzers; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.Medical.CrewMonitoring { @@ -18,7 +14,20 @@ namespace Content.Server.Medical.CrewMonitoring /// /// After what time sensor consider to be lost. /// - [DataField("sensorTimeout")] + [DataField("sensorTimeout"), ViewVariables(VVAccess.ReadWrite)] public float SensorTimeout = 10f; + + /// + /// Whether the direction arrows in the monitor UI should snap the nearest diagonal or cardinal direction, or whether they should point exactly towards the target. + /// + [DataField("snap"), ViewVariables(VVAccess.ReadWrite)] + public bool Snap = true; + + /// + /// Minimum distance before the monitor direction indicator stops pointing towards the target and instead + /// shows an icon indicating that the target is "here". Does not affect the displayed coordinates. + /// + [DataField("precision"), ViewVariables(VVAccess.ReadWrite)] + public float Precision = 10f; } } diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs index 7b9197220d..c99892f5b6 100644 --- a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs +++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs @@ -1,10 +1,10 @@ -using System.Linq; +using System.Linq; using Content.Server.DeviceNetwork.Systems; using Content.Server.Medical.SuitSensors; using Content.Server.UserInterface; using Content.Shared.Medical.CrewMonitoring; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; +using Content.Shared.Movement.Components; +using Robust.Shared.Map; using Robust.Shared.Timing; namespace Content.Server.Medical.CrewMonitoring @@ -13,6 +13,7 @@ namespace Content.Server.Medical.CrewMonitoring { [Dependency] private readonly SuitSensorSystem _sensors = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IMapManager _mapManager = default!; private const float UpdateRate = 3f; private float _updateDif; @@ -66,9 +67,22 @@ namespace Content.Server.Medical.CrewMonitoring if (ui == null) return; + // For directional arrows, we need to fetch the monitor's transform data + var xform = Transform(uid); + var (worldPos, worldRot) = xform.GetWorldPositionRotation(); + + // In general, the directions displayed depend on either the orientation of the grid, or the orientation of + // the monitor. But in the special case where the monitor IS a player (i.e., admin ghost), we base it off + // the players eye rotation. We don't know what that is for sure, but we know their last grid angle, which + // should work well enough? + if (TryComp(uid, out IMoverComponent? mover)) + worldRot = mover.LastGridAngle; + else if (_mapManager.TryGetGrid(xform.GridID, out var grid)) + worldRot = grid.WorldRotation; + // update all sensors info var allSensors = component.ConnectedSensors.Values.ToList(); - var uiState = new CrewMonitoringState(allSensors); + var uiState = new CrewMonitoringState(allSensors, worldPos, worldRot, component.Snap, component.Precision); ui.SetState(uiState); } diff --git a/Content.Shared/Medical/CrewMonitoring/CrewMonitoringShared.cs b/Content.Shared/Medical/CrewMonitoring/CrewMonitoringShared.cs index a8893f0a08..d1ee5edd96 100644 --- a/Content.Shared/Medical/CrewMonitoring/CrewMonitoringShared.cs +++ b/Content.Shared/Medical/CrewMonitoring/CrewMonitoringShared.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using Content.Shared.FixedPoint; using Content.Shared.Medical.SuitSensor; -using Robust.Shared.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Serialization; namespace Content.Shared.Medical.CrewMonitoring @@ -18,10 +13,18 @@ namespace Content.Shared.Medical.CrewMonitoring public sealed class CrewMonitoringState : BoundUserInterfaceState { public List Sensors; + public readonly Vector2 WorldPosition; + public readonly Angle WorldRotation; + public readonly bool Snap; + public readonly float Precision; - public CrewMonitoringState(List sensors) + public CrewMonitoringState(List sensors, Vector2 worldPosition, Angle worldRot, bool snap, float precision) { Sensors = sensors; + WorldPosition = worldPosition; + WorldRotation = worldRot; + Snap = snap; + Precision = precision; } } diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 2ffbf2385a..a3d84d1494 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -176,6 +176,8 @@ - AtmosphericsNitrogen - AtmosphericsCarbonDioxide - type: CrewMonitoringConsole + snap: false + precision: 3 - type: DeviceNetwork deviceNetId: Wireless receiveFrequencyId: SuitSensor diff --git a/Resources/Textures/Interface/VerbIcons/dot.svg b/Resources/Textures/Interface/VerbIcons/dot.svg new file mode 100644 index 0000000000..06647450f2 --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/dot.svg @@ -0,0 +1,48 @@ + + + + + + + + diff --git a/Resources/Textures/Interface/VerbIcons/dot.svg.192dpi.png b/Resources/Textures/Interface/VerbIcons/dot.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..2f42140d4b3c5f95c6effe5255a90827e701351e GIT binary patch literal 570 zcmV-A0>%A_P)Zd{yu&!-uA=30t~h=_=Yh=_>)OZB}Va8J@hNjD|kZod95X(nk@ zRp0vFQ|Ev+;3Y6-xqV<8SRH!b7Qho=)}r{LDe!pc{hu3f1$cXo;)^~3SBLI%CQbQ~ zq5P3uU}flDtpPj6@<-ke-K!Pm_k35j-21iu3<7JC4kg`i=AJNC*xB@S^f*Q}% zU3qE#?C{hX=)MBRlI}V)PqANB_g!JOD*)J3_vIyN6R@r0yagT@vwB<0Ie>LzR&Q%L z2XM=n)!SOm0btBpx)yNR5#$`e)R?t&t*ZLv2yzZ!V$52`Hf-krc8yufd~pTY22fRp zlE$vQzu2p)16P>)QTT&7?=&BsVeUuaYmyFheAeeSm9+8Cz6@2>Pf4$wnG3d^