Crew monitor revisit (#22240)
@@ -1,8 +1,7 @@
|
||||
using Content.Shared.Medical.CrewMonitoring;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
namespace Content.Client.Medical.CrewMonitoring;
|
||||
|
||||
public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
@@ -15,13 +14,19 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
protected override void Open()
|
||||
{
|
||||
EntityUid? gridUid = null;
|
||||
string stationName = string.Empty;
|
||||
|
||||
if (EntMan.TryGetComponent<TransformComponent>(Owner, out var xform))
|
||||
{
|
||||
gridUid = xform.GridUid;
|
||||
|
||||
if (EntMan.TryGetComponent<MetaDataComponent>(gridUid, out var metaData))
|
||||
{
|
||||
stationName = metaData.EntityName;
|
||||
}
|
||||
}
|
||||
|
||||
_menu = new CrewMonitoringWindow(gridUid);
|
||||
_menu = new CrewMonitoringWindow(stationName, gridUid);
|
||||
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
@@ -35,8 +40,7 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
case CrewMonitoringState st:
|
||||
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
|
||||
|
||||
_menu?.ShowSensors(st.Sensors, xform?.Coordinates, st.Snap, st.Precision);
|
||||
_menu?.ShowSensors(st.Sensors, Owner, xform?.Coordinates);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -50,4 +54,3 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using Content.Client.Pinpointer.UI;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Medical.CrewMonitoring;
|
||||
|
||||
public sealed partial class CrewMonitoringNavMapControl : NavMapControl
|
||||
{
|
||||
public NetEntity? Focus;
|
||||
public Dictionary<NetEntity, string> LocalizedNames = new();
|
||||
|
||||
private Color _backgroundColor;
|
||||
private Label _trackedEntityLabel;
|
||||
private PanelContainer _trackedEntityPanel;
|
||||
|
||||
public CrewMonitoringNavMapControl() : base()
|
||||
{
|
||||
WallColor = new Color(250, 146, 255);
|
||||
TileColor = new(71, 42, 72);
|
||||
|
||||
_backgroundColor = Color.FromSrgb(TileColor.WithAlpha(0.8f));
|
||||
|
||||
_trackedEntityLabel = new Label
|
||||
{
|
||||
Margin = new Thickness(10f, 8f),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Modulate = Color.White,
|
||||
};
|
||||
|
||||
_trackedEntityPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = _backgroundColor,
|
||||
},
|
||||
|
||||
Margin = new Thickness(5f, 10f),
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
Visible = false,
|
||||
};
|
||||
|
||||
_trackedEntityPanel.AddChild(_trackedEntityLabel);
|
||||
this.AddChild(_trackedEntityPanel);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (Focus == null)
|
||||
{
|
||||
_trackedEntityLabel.Text = string.Empty;
|
||||
_trackedEntityPanel.Visible = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ((var netEntity, var blip) in TrackedEntities)
|
||||
{
|
||||
if (netEntity != Focus)
|
||||
continue;
|
||||
|
||||
if (!LocalizedNames.TryGetValue(netEntity, out var name))
|
||||
name = "Unknown";
|
||||
|
||||
var message = name + "\nLocation: [x = " + MathF.Round(blip.Coordinates.X) + ", y = " + MathF.Round(blip.Coordinates.Y) + "]";
|
||||
|
||||
_trackedEntityLabel.Text = message;
|
||||
_trackedEntityPanel.Visible = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_trackedEntityLabel.Text = string.Empty;
|
||||
_trackedEntityPanel.Visible = false;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,29 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'crew-monitoring-user-interface-title'}"
|
||||
SetSize="1130 700"
|
||||
MinSize="1130 700">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<ScrollContainer HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="8, 8, 8, 8">
|
||||
<GridContainer Name="SensorsTable"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
HSeparationOverride="5"
|
||||
VSeparationOverride="20"
|
||||
Columns="4">
|
||||
<!-- Table header -->
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-name'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-job'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-status'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-location'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
SetSize="1200 700"
|
||||
MinSize="1200 700">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
|
||||
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20"/>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<controls:StripeBack>
|
||||
<PanelContainer>
|
||||
<Label Name="StationName" Text="Unknown station" Align="Center" />
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<ScrollContainer Name="SensorScroller"
|
||||
VerticalExpand="True"
|
||||
SetWidth="520"
|
||||
Margin="8, 8, 8, 8">
|
||||
<BoxContainer Name="SensorsTable"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 0 10 0">
|
||||
<!-- Table rows are filled by code -->
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
<Label Name="NoServerLabel"
|
||||
Text="{Loc 'crew-monitoring-user-interface-no-server'}"
|
||||
StyleClasses="LabelHeading"
|
||||
@@ -33,7 +31,19 @@
|
||||
HorizontalAlignment="Center"
|
||||
Visible="false"/>
|
||||
</ScrollContainer>
|
||||
<ui:NavMapControl Name="NavMap"
|
||||
Margin="5 5"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-flavor-left'}" StyleClasses="WindowFooterText" />
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-flavor-right'}" StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
|
||||
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,275 +1,437 @@
|
||||
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.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.Medical.CrewMonitoring
|
||||
{
|
||||
namespace Content.Client.Medical.CrewMonitoring;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
{
|
||||
private List<Control> _rowsContent = new();
|
||||
private List<(DirectionIcon Icon, Vector2 Position)> _directionIcons = new();
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IEyeManager _eye;
|
||||
private EntityUid? _stationUid;
|
||||
private CrewMonitoringButton? _trackedButton;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public static int IconSize = 16; // XAML has a `VSeparationOverride` of 20 for each row.
|
||||
private NetEntity? _trackedEntity;
|
||||
private bool _tryToScrollToListFocus;
|
||||
private Texture? _blipTexture;
|
||||
|
||||
public CrewMonitoringWindow(EntityUid? mapUid)
|
||||
public CrewMonitoringWindow(string stationName, EntityUid? mapUid)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_eye = IoCManager.Resolve<IEyeManager>();
|
||||
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_stationUid = mapUid;
|
||||
_prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
_blipTexture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
|
||||
|
||||
if (_entManager.TryGetComponent<TransformComponent>(mapUid, out var xform))
|
||||
{
|
||||
NavMap.MapUid = xform.GridUid;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
NavMap.Visible = false;
|
||||
SetSize = new Vector2(775, 400);
|
||||
MinSize = SetSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowSensors(List<SuitSensorStatus> stSensors, EntityCoordinates? monitorCoords, bool snap, float precision)
|
||||
{
|
||||
ClearAllSensors();
|
||||
StationName.AddStyleClass("LabelBig");
|
||||
StationName.Text = stationName;
|
||||
|
||||
var monitorCoordsInStationSpace = _stationUid != null ? monitorCoords?.WithEntityId(_stationUid.Value, _entManager).Position : null;
|
||||
|
||||
// TODO scroll container
|
||||
// TODO filter by name & occupation
|
||||
// TODO make each row a xaml-control. Get rid of some of this c# control creation.
|
||||
if (stSensors.Count == 0)
|
||||
{
|
||||
NoServerLabel.Visible = true;
|
||||
return;
|
||||
}
|
||||
NoServerLabel.Visible = false;
|
||||
|
||||
// add a row for each sensor
|
||||
foreach (var sensor in stSensors.OrderBy(a => a.Name))
|
||||
{
|
||||
var sensorEntity = _entManager.GetEntity(sensor.SuitSensorUid);
|
||||
var coordinates = _entManager.GetCoordinates(sensor.Coordinates);
|
||||
|
||||
// add button with username
|
||||
var nameButton = new CrewMonitoringButton()
|
||||
{
|
||||
SuitSensorUid = sensorEntity,
|
||||
Coordinates = coordinates,
|
||||
Text = sensor.Name,
|
||||
Margin = new Thickness(5f, 5f),
|
||||
};
|
||||
if (sensorEntity == _trackedButton?.SuitSensorUid)
|
||||
nameButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
SetColorLabel(nameButton.Label, sensor.TotalDamage, sensor.IsAlive);
|
||||
SensorsTable.AddChild(nameButton);
|
||||
_rowsContent.Add(nameButton);
|
||||
|
||||
// add users job
|
||||
// format: JobName
|
||||
var jobLabel = new Label()
|
||||
{
|
||||
Text = sensor.Job,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
SetColorLabel(jobLabel, sensor.TotalDamage, sensor.IsAlive);
|
||||
SensorsTable.AddChild(jobLabel);
|
||||
_rowsContent.Add(jobLabel);
|
||||
|
||||
// add users status and damage
|
||||
// format: IsAlive (TotalDamage)
|
||||
var statusText = Loc.GetString(sensor.IsAlive ?
|
||||
"crew-monitoring-user-interface-alive" :
|
||||
"crew-monitoring-user-interface-dead");
|
||||
if (sensor.TotalDamage != null)
|
||||
{
|
||||
statusText += $" ({sensor.TotalDamage})";
|
||||
}
|
||||
var statusLabel = new Label()
|
||||
{
|
||||
Text = statusText
|
||||
};
|
||||
SetColorLabel(statusLabel, sensor.TotalDamage, sensor.IsAlive);
|
||||
SensorsTable.AddChild(statusLabel);
|
||||
_rowsContent.Add(statusLabel);
|
||||
|
||||
// add users positions
|
||||
// format: (x, y)
|
||||
var box = GetPositionBox(sensor, monitorCoordsInStationSpace ?? Vector2.Zero, snap, precision);
|
||||
|
||||
SensorsTable.AddChild(box);
|
||||
_rowsContent.Add(box);
|
||||
|
||||
if (coordinates != null && NavMap.Visible)
|
||||
{
|
||||
NavMap.TrackedCoordinates.TryAdd(coordinates.Value,
|
||||
(true, sensorEntity == _trackedButton?.SuitSensorUid ? StyleNano.PointGreen : StyleNano.PointRed));
|
||||
|
||||
nameButton.OnButtonUp += args =>
|
||||
{
|
||||
if (_trackedButton != null && _trackedButton?.Coordinates != null)
|
||||
//Make previous point red
|
||||
NavMap.TrackedCoordinates[_trackedButton.Coordinates.Value] = (true, StyleNano.PointRed);
|
||||
|
||||
NavMap.TrackedCoordinates[coordinates.Value] = (true, StyleNano.PointGreen);
|
||||
NavMap.CenterToCoordinates(coordinates.Value);
|
||||
|
||||
nameButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
if (_trackedButton != null)
|
||||
{ //Make previous button default
|
||||
var previosButton = SensorsTable.GetChild(_trackedButton.IndexInTable);
|
||||
previosButton.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
}
|
||||
_trackedButton = nameButton;
|
||||
_trackedButton.IndexInTable = nameButton.GetPositionInParent();
|
||||
};
|
||||
}
|
||||
}
|
||||
// Show monitor point
|
||||
if (monitorCoords != null)
|
||||
NavMap.TrackedCoordinates.Add(monitorCoords.Value, (true, StyleNano.PointMagenta));
|
||||
}
|
||||
|
||||
private BoxContainer GetPositionBox(SuitSensorStatus sensor, Vector2 monitorCoordsInStationSpace, bool snap, float precision)
|
||||
{
|
||||
EntityCoordinates? coordinates = _entManager.GetCoordinates(sensor.Coordinates);
|
||||
var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal };
|
||||
|
||||
if (coordinates == null || _stationUid == null)
|
||||
{
|
||||
var dirIcon = new DirectionIcon()
|
||||
{
|
||||
SetSize = new Vector2(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
|
||||
{
|
||||
var local = coordinates.Value.WithEntityId(_stationUid.Value, _entManager).Position;
|
||||
|
||||
var displayPos = local.Floored();
|
||||
var dirIcon = new DirectionIcon(snap, precision)
|
||||
{
|
||||
SetSize = new Vector2(IconSize, IconSize),
|
||||
Margin = new(0, 0, 4, 0)
|
||||
};
|
||||
box.AddChild(dirIcon);
|
||||
Label label = new Label() { Text = displayPos.ToString() };
|
||||
SetColorLabel(label, sensor.TotalDamage, sensor.IsAlive);
|
||||
box.AddChild(label);
|
||||
_directionIcons.Add((dirIcon, local - monitorCoordsInStationSpace));
|
||||
}
|
||||
|
||||
return box;
|
||||
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
|
||||
NavMap.ForceNavMapUpdate();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
// the window is separate from any specific viewport, so there is no real way to get an eye-rotation without
|
||||
// using IEyeManager. Eventually this will have to be reworked for a station AI with multi-viewports.
|
||||
// (From the future: Or alternatively, just disable the angular offset for station AIs?)
|
||||
base.FrameUpdate(args);
|
||||
|
||||
// An offsetAngle of zero here perfectly aligns directions to the station map.
|
||||
// Note that the "relative angle" does this weird inverse-inverse thing.
|
||||
// Could recalculate it all in world coordinates and then pass in eye directly... or do this.
|
||||
var offsetAngle = Angle.Zero;
|
||||
if (_entManager.TryGetComponent<TransformComponent>(_stationUid, out var xform))
|
||||
{
|
||||
// Apply the offset relative to the eye.
|
||||
// For a station at 45 degrees rotation, the current eye rotation is -45 degrees.
|
||||
// TODO: This feels sketchy. Is there something underlying wrong with eye rotation?
|
||||
offsetAngle = -(_eye.CurrentEye.Rotation + xform.WorldRotation);
|
||||
if (_tryToScrollToListFocus)
|
||||
TryToScrollToFocus();
|
||||
}
|
||||
|
||||
foreach (var (icon, pos) in _directionIcons)
|
||||
public void ShowSensors(List<SuitSensorStatus> sensors, EntityUid monitor, EntityCoordinates? monitorCoords)
|
||||
{
|
||||
icon.UpdateDirection(pos, offsetAngle);
|
||||
}
|
||||
}
|
||||
ClearOutDatedData();
|
||||
|
||||
private void ClearAllSensors()
|
||||
// No server label
|
||||
if (sensors.Count == 0)
|
||||
{
|
||||
foreach (var child in _rowsContent)
|
||||
{
|
||||
SensorsTable.RemoveChild(child);
|
||||
}
|
||||
_rowsContent.Clear();
|
||||
_directionIcons.Clear();
|
||||
NavMap.TrackedCoordinates.Clear();
|
||||
}
|
||||
|
||||
private void SetColorLabel(Label label, int? totalDamage, bool isAlive)
|
||||
{
|
||||
var startColor = Color.White;
|
||||
var critColor = Color.Yellow;
|
||||
var endColor = Color.Red;
|
||||
|
||||
if (!isAlive)
|
||||
{
|
||||
label.FontColorOverride = endColor;
|
||||
NoServerLabel.Visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
//Convert from null to regular int
|
||||
int damage;
|
||||
if (totalDamage == null) return;
|
||||
else damage = (int) totalDamage;
|
||||
NoServerLabel.Visible = false;
|
||||
|
||||
if (damage <= 0)
|
||||
// Order sensor data
|
||||
var orderedSensors = sensors.OrderBy(n => n.Name).OrderBy(j => j.Job);
|
||||
var assignedSensors = new HashSet<SuitSensorStatus>();
|
||||
var departments = sensors.SelectMany(d => d.JobDepartments).Distinct().OrderBy(n => n);
|
||||
|
||||
// Create department labels and populate lists
|
||||
foreach (var department in departments)
|
||||
{
|
||||
label.FontColorOverride = startColor;
|
||||
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);
|
||||
_rowsContent.Add(spacer);
|
||||
}
|
||||
else if (damage >= 200)
|
||||
|
||||
var deparmentLabel = new RichTextLabel()
|
||||
{
|
||||
label.FontColorOverride = endColor;
|
||||
Margin = new Thickness(10, 0),
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
deparmentLabel.SetMessage(department);
|
||||
deparmentLabel.StyleClasses.Add(StyleNano.StyleClassTooltipActionDescription);
|
||||
|
||||
SensorsTable.AddChild(deparmentLabel);
|
||||
_rowsContent.Add(deparmentLabel);
|
||||
|
||||
PopulateDepartmentList(departmentSensors);
|
||||
}
|
||||
else if (damage >= 0 && damage <= 100)
|
||||
|
||||
// Account for any non-station users
|
||||
var remainingSensors = orderedSensors.Except(assignedSensors);
|
||||
|
||||
if (remainingSensors.Any())
|
||||
{
|
||||
label.FontColorOverride = GetColorLerp(startColor, critColor, damage);
|
||||
var spacer = new Control()
|
||||
{
|
||||
SetHeight = 20,
|
||||
};
|
||||
|
||||
SensorsTable.AddChild(spacer);
|
||||
_rowsContent.Add(spacer);
|
||||
|
||||
var deparmentLabel = new RichTextLabel()
|
||||
{
|
||||
Margin = new Thickness(10, 0),
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
deparmentLabel.SetMessage(Loc.GetString("crew-monitoring-user-interface-no-department"));
|
||||
deparmentLabel.StyleClasses.Add(StyleNano.StyleClassTooltipActionDescription);
|
||||
|
||||
SensorsTable.AddChild(deparmentLabel);
|
||||
_rowsContent.Add(deparmentLabel);
|
||||
|
||||
PopulateDepartmentList(remainingSensors);
|
||||
}
|
||||
else if (damage >= 100 && damage <= 200)
|
||||
|
||||
// Show monitor on nav map
|
||||
if (monitorCoords != null && _blipTexture != null)
|
||||
{
|
||||
//We need a number from 0 to 100. Divide the number from 100 to 200 by 2
|
||||
damage /= 2;
|
||||
label.FontColorOverride = GetColorLerp(critColor, endColor, damage);
|
||||
NavMap.TrackedEntities[_entManager.GetNetEntity(monitor)] = new NavMapBlip(monitorCoords.Value, _blipTexture, Color.Cyan, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetColorLerp(Color startColor, Color endColor, int damage)
|
||||
private void PopulateDepartmentList(IEnumerable<SuitSensorStatus> departmentSensors)
|
||||
{
|
||||
//Smooth transition from one color to another depending on the percentage
|
||||
var t = damage / 100f;
|
||||
var r = MathHelper.Lerp(startColor.R, endColor.R, t);
|
||||
var g = MathHelper.Lerp(startColor.G, endColor.G, t);
|
||||
var b = MathHelper.Lerp(startColor.B, endColor.B, t);
|
||||
var a = MathHelper.Lerp(startColor.A, endColor.A, t);
|
||||
// Populate departments
|
||||
foreach (var sensor in departmentSensors)
|
||||
{
|
||||
var coordinates = _entManager.GetCoordinates(sensor.Coordinates);
|
||||
|
||||
return new Color(r, g, b, a);
|
||||
// Add a button that will hold a username and other details
|
||||
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(StyleNano.StyleClassButtonColorGreen);
|
||||
|
||||
SensorsTable.AddChild(sensorButton);
|
||||
_rowsContent.Add(sensorButton);
|
||||
|
||||
// Primary container to hold the button UI elements
|
||||
var mainContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
sensorButton.AddChild(mainContainer);
|
||||
|
||||
// User status container
|
||||
var statusContainer = new BoxContainer()
|
||||
{
|
||||
SizeFlagsStretchRatio = 1.25f,
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
mainContainer.AddChild(statusContainer);
|
||||
|
||||
// Suit coords indicator
|
||||
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);
|
||||
|
||||
// Specify texture for the user status icon
|
||||
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.TotalDamage != null)
|
||||
{
|
||||
var index = MathF.Round(4f * (sensor.TotalDamage.Value / 100f));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Status icon
|
||||
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);
|
||||
|
||||
// User name
|
||||
var nameLabel = new Label()
|
||||
{
|
||||
Text = sensor.Name,
|
||||
HorizontalExpand = true,
|
||||
ClipText = true,
|
||||
};
|
||||
|
||||
statusContainer.AddChild(nameLabel);
|
||||
|
||||
// User job container
|
||||
var jobContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
mainContainer.AddChild(jobContainer);
|
||||
|
||||
// Job icon
|
||||
if (_prototypeManager.TryIndex<StatusIconPrototype>(sensor.JobIcon, out var proto))
|
||||
{
|
||||
var jobIcon = new TextureRect()
|
||||
{
|
||||
TextureScale = new Vector2(2f, 2f),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
Texture = _spriteSystem.Frame0(proto.Icon),
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
};
|
||||
|
||||
jobContainer.AddChild(jobIcon);
|
||||
}
|
||||
|
||||
// Job name
|
||||
var jobLabel = new Label()
|
||||
{
|
||||
Text = sensor.Job,
|
||||
HorizontalExpand = true,
|
||||
ClipText = true,
|
||||
};
|
||||
|
||||
jobContainer.AddChild(jobLabel);
|
||||
|
||||
// Add user coordinates to the navmap
|
||||
if (coordinates != null && NavMap.Visible && _blipTexture != null)
|
||||
{
|
||||
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
|
||||
new NavMapBlip
|
||||
(coordinates.Value,
|
||||
_blipTexture,
|
||||
(_trackedEntity == null || sensor.SuitSensorUid == _trackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
|
||||
sensor.SuitSensorUid == _trackedEntity));
|
||||
|
||||
NavMap.Focus = _trackedEntity;
|
||||
|
||||
// On button up
|
||||
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(StyleNano.StyleClassButtonColorGreen);
|
||||
|
||||
else if (castSensor.SuitSensorUid == currTrackedEntity)
|
||||
castSensor.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
|
||||
if (castSensor?.Coordinates == null)
|
||||
continue;
|
||||
|
||||
if (NavMap.TrackedEntities.TryGetValue(castSensor.SuitSensorUid, out var data))
|
||||
{
|
||||
data = new NavMapBlip
|
||||
(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 (!TryGetVerticalScrollbar(SensorScroller, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
{
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
{
|
||||
_tryToScrollToListFocus = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var child in scroll.Children)
|
||||
{
|
||||
if (child is not VScrollBar)
|
||||
continue;
|
||||
|
||||
vScrollBar = (VScrollBar) child;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = 0;
|
||||
|
||||
foreach (var sensor in SensorsTable.Children)
|
||||
{
|
||||
if (sensor is CrewMonitoringButton &&
|
||||
((CrewMonitoringButton) sensor).SuitSensorUid == _trackedEntity)
|
||||
return true;
|
||||
|
||||
nextScrollPosition += sensor.Height;
|
||||
}
|
||||
|
||||
// Failed to find control
|
||||
nextScrollPosition = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ClearOutDatedData()
|
||||
{
|
||||
SensorsTable.RemoveAllChildren();
|
||||
_rowsContent.Clear();
|
||||
NavMap.TrackedCoordinates.Clear();
|
||||
NavMap.TrackedEntities.Clear();
|
||||
NavMap.LocalizedNames.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CrewMonitoringButton : Button
|
||||
{
|
||||
public int IndexInTable;
|
||||
public EntityUid? SuitSensorUid;
|
||||
public NetEntity SuitSensorUid;
|
||||
public EntityCoordinates? Coordinates;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,66 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Displays the nav map data of the specified grid.
|
||||
/// </summary>
|
||||
public sealed class NavMapControl : MapGridControl
|
||||
[UsedImplicitly, Virtual]
|
||||
public partial class NavMapControl : MapGridControl
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private SharedTransformSystem _transform;
|
||||
private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
public EntityUid? MapUid;
|
||||
|
||||
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
|
||||
// Actions
|
||||
public event Action<NetEntity?>? TrackedEntitySelectedAction;
|
||||
|
||||
// Tracked data
|
||||
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
|
||||
public Dictionary<NetEntity, NavMapBlip> TrackedEntities = new();
|
||||
public Dictionary<Vector2i, List<NavMapLine>> TileGrid = default!;
|
||||
|
||||
// Default colors
|
||||
public Color WallColor = new(102, 217, 102);
|
||||
public Color TileColor = new(30, 67, 30);
|
||||
|
||||
// Constants
|
||||
protected float UpdateTime = 1.0f;
|
||||
protected float MaxSelectableDistance = 10f;
|
||||
protected float RecenterMinimum = 0.05f;
|
||||
|
||||
// Local variables
|
||||
private Vector2 _offset;
|
||||
private bool _draggin;
|
||||
private bool _recentering = false;
|
||||
private readonly float _recenterMinimum = 0.05f;
|
||||
private readonly Font _font;
|
||||
private static readonly Color TileColor = new(30, 67, 30);
|
||||
private static readonly Color BeaconColor = Color.FromSrgb(TileColor.WithAlpha(0.8f));
|
||||
private float _updateTimer = 0.25f;
|
||||
private Dictionary<Color, Color> _sRGBLookUp = new Dictionary<Color, Color>();
|
||||
private Color _beaconColor;
|
||||
|
||||
// Components
|
||||
private NavMapComponent? _navMap;
|
||||
private MapGridComponent? _grid;
|
||||
private TransformComponent? _xform;
|
||||
private PhysicsComponent? _physics;
|
||||
private FixturesComponent? _fixtures;
|
||||
|
||||
// TODO: https://github.com/space-wizards/RobustToolbox/issues/3818
|
||||
private readonly Label _zoom = new()
|
||||
@@ -45,20 +71,30 @@ public sealed class NavMapControl : MapGridControl
|
||||
|
||||
private readonly Button _recenter = new()
|
||||
{
|
||||
Text = "Recentre",
|
||||
Text = Loc.GetString("navmap-recenter"),
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
Margin = new Thickness(8f, 4f),
|
||||
Disabled = true,
|
||||
};
|
||||
|
||||
private readonly CheckBox _beacons = new()
|
||||
{
|
||||
Text = Loc.GetString("navmap-toggle-beacons"),
|
||||
Margin = new Thickness(4f, 0f),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Pressed = false,
|
||||
};
|
||||
|
||||
public NavMapControl() : base(8f, 128f, 48f)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_transform = _entManager.System<SharedTransformSystem>();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 16);
|
||||
|
||||
_transformSystem = _entManager.System<SharedTransformSystem>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 12);
|
||||
_beaconColor = Color.FromSrgb(TileColor.WithAlpha(0.8f));
|
||||
|
||||
RectClipContent = true;
|
||||
HorizontalExpand = true;
|
||||
@@ -75,6 +111,7 @@ public sealed class NavMapControl : MapGridControl
|
||||
Children =
|
||||
{
|
||||
_zoom,
|
||||
_beacons,
|
||||
_recenter,
|
||||
}
|
||||
};
|
||||
@@ -101,14 +138,28 @@ public sealed class NavMapControl : MapGridControl
|
||||
{
|
||||
_recentering = true;
|
||||
};
|
||||
|
||||
ForceNavMapUpdate();
|
||||
}
|
||||
|
||||
public void ForceRecenter()
|
||||
{
|
||||
_recentering = true;
|
||||
}
|
||||
|
||||
public void ForceNavMapUpdate()
|
||||
{
|
||||
_entManager.TryGetComponent(MapUid, out _navMap);
|
||||
_entManager.TryGetComponent(MapUid, out _grid);
|
||||
|
||||
UpdateNavMap();
|
||||
}
|
||||
|
||||
public void CenterToCoordinates(EntityCoordinates coordinates)
|
||||
{
|
||||
if (_entManager.TryGetComponent<PhysicsComponent>(MapUid, out var physics))
|
||||
{
|
||||
_offset = new Vector2(coordinates.X, coordinates.Y) - physics.LocalCenter;
|
||||
}
|
||||
if (_physics != null)
|
||||
_offset = new Vector2(coordinates.X, coordinates.Y) - _physics.LocalCenter;
|
||||
|
||||
_recenter.Disabled = false;
|
||||
}
|
||||
|
||||
@@ -117,18 +168,62 @@ public sealed class NavMapControl : MapGridControl
|
||||
base.KeyBindDown(args);
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use)
|
||||
{
|
||||
_draggin = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
|
||||
if (TrackedEntitySelectedAction == null)
|
||||
return;
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use)
|
||||
{
|
||||
_draggin = false;
|
||||
|
||||
if (_xform == null || _physics == null || TrackedEntities.Count == 0)
|
||||
return;
|
||||
|
||||
// Get the clicked position
|
||||
var offset = _offset + _physics.LocalCenter;
|
||||
var localPosition = args.PointerLocation.Position - GlobalPixelPosition;
|
||||
|
||||
// Convert to a world position
|
||||
var unscaledPosition = (localPosition - MidpointVector) / MinimapScale;
|
||||
var worldPosition = _transformSystem.GetWorldMatrix(_xform).Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset);
|
||||
|
||||
// Find closest tracked entity in range
|
||||
var closestEntity = NetEntity.Invalid;
|
||||
var closestCoords = new EntityCoordinates();
|
||||
var closestDistance = float.PositiveInfinity;
|
||||
|
||||
foreach ((var currentEntity, var blip) in TrackedEntities)
|
||||
{
|
||||
if (!blip.Selectable)
|
||||
continue;
|
||||
|
||||
var currentDistance = (blip.Coordinates.ToMapPos(_entManager, _transformSystem) - worldPosition).Length();
|
||||
|
||||
if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance)
|
||||
continue;
|
||||
|
||||
closestEntity = currentEntity;
|
||||
closestCoords = blip.Coordinates;
|
||||
closestDistance = currentDistance;
|
||||
}
|
||||
|
||||
if (closestDistance > MaxSelectableDistance || !closestEntity.IsValid())
|
||||
return;
|
||||
|
||||
TrackedEntitySelectedAction.Invoke(closestEntity);
|
||||
}
|
||||
|
||||
else if (args.Function == EngineKeyFunctions.UIRightClick)
|
||||
{
|
||||
// Clear current selection with right click
|
||||
if (TrackedEntitySelectedAction != null)
|
||||
TrackedEntitySelectedAction.Invoke(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,25 +238,30 @@ public sealed class NavMapControl : MapGridControl
|
||||
_offset -= new Vector2(args.Relative.X, -args.Relative.Y) / MidPoint * WorldRange;
|
||||
|
||||
if (_offset != Vector2.Zero)
|
||||
{
|
||||
_recenter.Disabled = false;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_recenter.Disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
// Get the components necessary for drawing the navmap
|
||||
_entManager.TryGetComponent(MapUid, out _navMap);
|
||||
_entManager.TryGetComponent(MapUid, out _grid);
|
||||
_entManager.TryGetComponent(MapUid, out _xform);
|
||||
_entManager.TryGetComponent(MapUid, out _physics);
|
||||
_entManager.TryGetComponent(MapUid, out _fixtures);
|
||||
|
||||
// Map re-centering
|
||||
if (_recentering)
|
||||
{
|
||||
var frameTime = Timing.FrameTime;
|
||||
var diff = _offset * (float) frameTime.TotalSeconds;
|
||||
|
||||
if (_offset.LengthSquared() < _recenterMinimum)
|
||||
if (_offset.LengthSquared() < RecenterMinimum)
|
||||
{
|
||||
_offset = Vector2.Zero;
|
||||
_recentering = false;
|
||||
@@ -173,29 +273,22 @@ public sealed class NavMapControl : MapGridControl
|
||||
}
|
||||
}
|
||||
|
||||
_zoom.Text = $"Zoom: {(WorldRange / WorldMaxRange * 100f):0.00}%";
|
||||
_zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(WorldRange / WorldMaxRange * 100f):0.00}"));
|
||||
|
||||
if (!_entManager.TryGetComponent<NavMapComponent>(MapUid, out var navMap) ||
|
||||
!_entManager.TryGetComponent<TransformComponent>(MapUid, out var xform) ||
|
||||
!_entManager.TryGetComponent<MapGridComponent>(MapUid, out var grid))
|
||||
{
|
||||
if (_navMap == null || _xform == null)
|
||||
return;
|
||||
}
|
||||
|
||||
var offset = _offset;
|
||||
var lineColor = new Color(102, 217, 102);
|
||||
|
||||
if (_entManager.TryGetComponent<PhysicsComponent>(MapUid, out var physics))
|
||||
{
|
||||
offset += physics.LocalCenter;
|
||||
}
|
||||
if (_physics != null)
|
||||
offset += _physics.LocalCenter;
|
||||
|
||||
// Draw tiles
|
||||
if (_entManager.TryGetComponent<FixturesComponent>(MapUid, out var manager))
|
||||
if (_fixtures != null)
|
||||
{
|
||||
Span<Vector2> verts = new Vector2[8];
|
||||
|
||||
foreach (var fixture in manager.Fixtures.Values)
|
||||
foreach (var fixture in _fixtures.Fixtures.Values)
|
||||
{
|
||||
if (fixture.Shape is not PolygonShape poly)
|
||||
continue;
|
||||
@@ -211,21 +304,165 @@ public sealed class NavMapControl : MapGridControl
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the wall data
|
||||
var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
|
||||
var tileSize = new Vector2(grid.TileSize, -grid.TileSize);
|
||||
|
||||
for (var x = Math.Floor(area.Left); x <= Math.Ceiling(area.Right); x += SharedNavMapSystem.ChunkSize * grid.TileSize)
|
||||
// Drawing lines can be rather expensive due to the number of neighbors that need to be checked in order
|
||||
// to figure out where they should be drawn. However, we don't *need* to do check these every frame.
|
||||
// Instead, lets periodically update where to draw each line and then store these points in a list.
|
||||
// Then we can just run through the list each frame and draw the lines without any extra computation.
|
||||
|
||||
// Draw walls
|
||||
if (TileGrid != null && TileGrid.Count > 0)
|
||||
{
|
||||
for (var y = Math.Floor(area.Bottom); y <= Math.Ceiling(area.Top); y += SharedNavMapSystem.ChunkSize * grid.TileSize)
|
||||
var walls = new ValueList<Vector2>();
|
||||
|
||||
foreach ((var chunk, var chunkedLines) in TileGrid)
|
||||
{
|
||||
var floored = new Vector2i((int) x, (int) y);
|
||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
||||
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize);
|
||||
|
||||
if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
||||
continue;
|
||||
|
||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
||||
continue;
|
||||
|
||||
foreach (var chunkedLine in chunkedLines)
|
||||
{
|
||||
var start = Scale(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
|
||||
var end = Scale(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
|
||||
|
||||
walls.Add(start);
|
||||
walls.Add(end);
|
||||
}
|
||||
}
|
||||
|
||||
if (walls.Count > 0)
|
||||
{
|
||||
if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB))
|
||||
{
|
||||
sRGB = Color.ToSrgb(WallColor);
|
||||
_sRGBLookUp[WallColor] = sRGB;
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, walls.Span, sRGB);
|
||||
}
|
||||
}
|
||||
|
||||
// Beacons
|
||||
if (_beacons.Pressed)
|
||||
{
|
||||
var rectBuffer = new Vector2(5f, 3f);
|
||||
|
||||
foreach (var beacon in _navMap.Beacons)
|
||||
{
|
||||
var position = beacon.Position - offset;
|
||||
position = Scale(position with { Y = -position.Y });
|
||||
|
||||
var textDimensions = handle.GetDimensions(_font, beacon.Text, 1f);
|
||||
handle.DrawRect(new UIBox2(position - textDimensions / 2 - rectBuffer, position + textDimensions / 2 + rectBuffer), _beaconColor);
|
||||
handle.DrawString(_font, position - textDimensions / 2, beacon.Text, beacon.Color);
|
||||
}
|
||||
}
|
||||
|
||||
var curTime = Timing.RealTime;
|
||||
var blinkFrequency = 1f / 1f;
|
||||
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
|
||||
|
||||
// Tracked coordinates (simple dot, legacy)
|
||||
foreach (var (coord, value) in TrackedCoordinates)
|
||||
{
|
||||
if (lit && value.Visible)
|
||||
{
|
||||
var mapPos = coord.ToMap(_entManager, _transformSystem);
|
||||
|
||||
if (mapPos.MapId != MapId.Nullspace)
|
||||
{
|
||||
var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
|
||||
position = Scale(new Vector2(position.X, -position.Y));
|
||||
|
||||
handle.DrawCircle(position, float.Sqrt(MinimapScale) * 2f, value.Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tracked entities (can use a supplied sprite as a marker instead; should probably just replace TrackedCoordinates with this eventually)
|
||||
var iconVertexUVs = new Dictionary<(Texture, Color), ValueList<DrawVertexUV2D>>();
|
||||
|
||||
foreach (var blip in TrackedEntities.Values)
|
||||
{
|
||||
if (blip.Blinks && !lit)
|
||||
continue;
|
||||
|
||||
if (blip.Texture == null)
|
||||
continue;
|
||||
|
||||
if (!iconVertexUVs.TryGetValue((blip.Texture, blip.Color), out var vertexUVs))
|
||||
vertexUVs = new();
|
||||
|
||||
var mapPos = blip.Coordinates.ToMap(_entManager, _transformSystem);
|
||||
|
||||
if (mapPos.MapId != MapId.Nullspace)
|
||||
{
|
||||
var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
|
||||
position = Scale(new Vector2(position.X, -position.Y));
|
||||
|
||||
var scalingCoefficient = 2.5f;
|
||||
var positionOffset = scalingCoefficient * float.Sqrt(MinimapScale);
|
||||
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y - positionOffset), new Vector2(1f, 1f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y + positionOffset), new Vector2(0f, 0f)));
|
||||
}
|
||||
|
||||
iconVertexUVs[(blip.Texture, blip.Color)] = vertexUVs;
|
||||
}
|
||||
|
||||
foreach ((var (texture, color), var vertexUVs) in iconVertexUVs)
|
||||
{
|
||||
if (!_sRGBLookUp.TryGetValue(color, out var sRGB))
|
||||
{
|
||||
sRGB = Color.ToSrgb(color);
|
||||
_sRGBLookUp[color] = sRGB;
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, texture, vertexUVs.Span, sRGB);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
// Update the timer
|
||||
_updateTimer += args.DeltaSeconds;
|
||||
|
||||
if (_updateTimer >= UpdateTime)
|
||||
{
|
||||
_updateTimer -= UpdateTime;
|
||||
|
||||
UpdateNavMap();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNavMap()
|
||||
{
|
||||
if (_navMap == null || _grid == null)
|
||||
return;
|
||||
|
||||
TileGrid = GetDecodedWallChunks(_navMap.Chunks, _grid);
|
||||
}
|
||||
|
||||
public Dictionary<Vector2i, List<NavMapLine>> GetDecodedWallChunks
|
||||
(Dictionary<Vector2i, NavMapChunk> chunks,
|
||||
MapGridComponent grid)
|
||||
{
|
||||
var decodedOutput = new Dictionary<Vector2i, List<NavMapLine>>();
|
||||
|
||||
foreach ((var chunkOrigin, var chunk) in chunks)
|
||||
{
|
||||
var list = new List<NavMapLine>();
|
||||
|
||||
// TODO: Okay maybe I should just use ushorts lmao...
|
||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
||||
{
|
||||
@@ -238,7 +475,7 @@ public sealed class NavMapControl : MapGridControl
|
||||
|
||||
// Alright now we'll work out our edges
|
||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize - offset;
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
|
||||
var position = new Vector2(tile.X, -tile.Y);
|
||||
NavMapChunk? neighborChunk;
|
||||
bool neighbor;
|
||||
@@ -246,7 +483,7 @@ public sealed class NavMapControl : MapGridControl
|
||||
// North edge
|
||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
||||
{
|
||||
neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
||||
(neighborChunk.TileData &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||
}
|
||||
@@ -258,13 +495,14 @@ public sealed class NavMapControl : MapGridControl
|
||||
|
||||
if (!neighbor)
|
||||
{
|
||||
handle.DrawLine(Scale(position + new Vector2(0f, -grid.TileSize)), Scale(position + tileSize), lineColor);
|
||||
// Add points
|
||||
list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, -grid.TileSize)));
|
||||
}
|
||||
|
||||
// East edge
|
||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
||||
{
|
||||
neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
||||
(neighborChunk.TileData &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||
}
|
||||
@@ -276,13 +514,14 @@ public sealed class NavMapControl : MapGridControl
|
||||
|
||||
if (!neighbor)
|
||||
{
|
||||
handle.DrawLine(Scale(position + tileSize), Scale(position + new Vector2(grid.TileSize, 0f)), lineColor);
|
||||
// Add points
|
||||
list.Add(new NavMapLine(position + new Vector2(grid.TileSize, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
|
||||
}
|
||||
|
||||
// South edge
|
||||
if (relativeTile.Y == 0)
|
||||
{
|
||||
neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) &&
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) &&
|
||||
(neighborChunk.TileData &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
|
||||
}
|
||||
@@ -294,13 +533,14 @@ public sealed class NavMapControl : MapGridControl
|
||||
|
||||
if (!neighbor)
|
||||
{
|
||||
handle.DrawLine(Scale(position + new Vector2(grid.TileSize, 0f)), Scale(position), lineColor);
|
||||
// Add points
|
||||
list.Add(new NavMapLine(position + new Vector2(grid.TileSize, 0f), position));
|
||||
}
|
||||
|
||||
// West edge
|
||||
if (relativeTile.X == 0)
|
||||
{
|
||||
neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) &&
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) &&
|
||||
(neighborChunk.TileData &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
|
||||
}
|
||||
@@ -312,56 +552,57 @@ public sealed class NavMapControl : MapGridControl
|
||||
|
||||
if (!neighbor)
|
||||
{
|
||||
handle.DrawLine(Scale(position), Scale(position + new Vector2(0f, -grid.TileSize)), lineColor);
|
||||
// Add point
|
||||
list.Add(new NavMapLine(position, position + new Vector2(0f, -grid.TileSize)));
|
||||
}
|
||||
|
||||
// Draw a diagonal line for interiors.
|
||||
handle.DrawLine(Scale(position + new Vector2(0f, -grid.TileSize)), Scale(position + new Vector2(grid.TileSize, 0f)), lineColor);
|
||||
}
|
||||
}
|
||||
list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
|
||||
}
|
||||
|
||||
var curTime = Timing.RealTime;
|
||||
var blinkFrequency = 1f / 1f;
|
||||
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
|
||||
|
||||
foreach (var (coord, value) in TrackedCoordinates)
|
||||
{
|
||||
if (lit && value.Visible)
|
||||
{
|
||||
var mapPos = coord.ToMap(_entManager);
|
||||
|
||||
if (mapPos.MapId != MapId.Nullspace)
|
||||
{
|
||||
var position = xform.InvWorldMatrix.Transform(mapPos.Position) - offset;
|
||||
position = Scale(new Vector2(position.X, -position.Y));
|
||||
|
||||
handle.DrawCircle(position, MinimapScale / 2f, value.Color);
|
||||
}
|
||||
}
|
||||
decodedOutput.Add(chunkOrigin, list);
|
||||
}
|
||||
|
||||
// Beacons
|
||||
var labelOffset = new Vector2(0.5f, 0.5f) * MinimapScale;
|
||||
var rectBuffer = new Vector2(5f, 3f);
|
||||
|
||||
foreach (var beacon in navMap.Beacons)
|
||||
{
|
||||
var position = beacon.Position - offset;
|
||||
|
||||
position = Scale(position with { Y = -position.Y });
|
||||
|
||||
handle.DrawCircle(position, MinimapScale / 2f, beacon.Color);
|
||||
var textDimensions = handle.GetDimensions(_font, beacon.Text, 1f);
|
||||
|
||||
var labelPosition = position + labelOffset;
|
||||
handle.DrawRect(new UIBox2(labelPosition, labelPosition + textDimensions + rectBuffer * 2), BeaconColor);
|
||||
handle.DrawString(_font, labelPosition + rectBuffer, beacon.Text, beacon.Color);
|
||||
}
|
||||
return decodedOutput;
|
||||
}
|
||||
|
||||
private Vector2 Scale(Vector2 position)
|
||||
protected Vector2 Scale(Vector2 position)
|
||||
{
|
||||
return position * MinimapScale + MidpointVector;
|
||||
}
|
||||
|
||||
protected Vector2 GetOffset()
|
||||
{
|
||||
return _offset + (_physics != null ? _physics.LocalCenter : new Vector2());
|
||||
}
|
||||
}
|
||||
|
||||
public struct NavMapBlip
|
||||
{
|
||||
public EntityCoordinates Coordinates;
|
||||
public Texture Texture;
|
||||
public Color Color;
|
||||
public bool Blinks;
|
||||
public bool Selectable;
|
||||
|
||||
public NavMapBlip(EntityCoordinates coordinates, Texture texture, Color color, bool blinks, bool selectable = true)
|
||||
{
|
||||
Coordinates = coordinates;
|
||||
Texture = texture;
|
||||
Color = color;
|
||||
Blinks = blinks;
|
||||
Selectable = selectable;
|
||||
}
|
||||
}
|
||||
|
||||
public struct NavMapLine
|
||||
{
|
||||
public readonly Vector2 Origin;
|
||||
public readonly Vector2 Terminus;
|
||||
|
||||
public NavMapLine(Vector2 origin, Vector2 terminus)
|
||||
{
|
||||
Origin = origin;
|
||||
Terminus = terminus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,5 +21,7 @@ public sealed partial class StationMapWindow : FancyWindow
|
||||
{
|
||||
Title = metadata.EntityName;
|
||||
}
|
||||
|
||||
NavMapScreen.ForceNavMapUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Access.Components
|
||||
{
|
||||
namespace Content.Server.Access.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PresetIdCardComponent : Component
|
||||
{
|
||||
@@ -12,4 +12,3 @@ namespace Content.Server.Access.Components
|
||||
[DataField("name")]
|
||||
public string? IdName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Access.Systems
|
||||
{
|
||||
namespace Content.Server.Access.Systems;
|
||||
|
||||
public sealed class IdCardSystem : SharedIdCardSystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
@@ -143,6 +144,22 @@ namespace Content.Server.Access.Systems
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryChangeJobDepartment(EntityUid uid, JobPrototype job, IdCardComponent? id = null)
|
||||
{
|
||||
if (!Resolve(uid, ref id))
|
||||
return false;
|
||||
|
||||
foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
if (department.Roles.Contains(job.ID))
|
||||
id.JobDepartments.Add("department-" + department.ID);
|
||||
}
|
||||
|
||||
Dirty(uid, id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to change the full name of a card.
|
||||
/// Returns true/false.
|
||||
@@ -203,4 +220,3 @@ namespace Content.Server.Access.Systems
|
||||
_metaSystem.SetEntityName(uid, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ using Content.Shared.Roles;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Access.Systems
|
||||
{
|
||||
namespace Content.Server.Access.Systems;
|
||||
|
||||
public sealed class PresetIdCardSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -78,6 +78,7 @@ namespace Content.Server.Access.Systems
|
||||
_accessSystem.SetAccessToJob(uid, job, extended);
|
||||
|
||||
_cardSystem.TryChangeJobTitle(uid, job.LocalizedName);
|
||||
_cardSystem.TryChangeJobDepartment(uid, job);
|
||||
|
||||
if (_prototypeManager.TryIndex<StatusIconPrototype>(job.Icon, out var jobIcon))
|
||||
{
|
||||
@@ -85,4 +86,3 @@ namespace Content.Server.Access.Systems
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
|
||||
namespace Content.Server.Medical.CrewMonitoring
|
||||
{
|
||||
namespace Content.Server.Medical.CrewMonitoring;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(CrewMonitoringConsoleSystem))]
|
||||
public sealed partial class CrewMonitoringConsoleComponent : Component
|
||||
@@ -16,18 +16,4 @@ namespace Content.Server.Medical.CrewMonitoring
|
||||
/// </summary>
|
||||
[DataField("sensorTimeout"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SensorTimeout = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("snap"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Snap = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("precision"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Precision = 10f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Shared.Medical.CrewMonitoring;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Medical.CrewMonitoring
|
||||
{
|
||||
namespace Content.Server.Medical.CrewMonitoring;
|
||||
|
||||
public sealed class CrewMonitoringConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PowerCellSystem _cell = default!;
|
||||
@@ -29,11 +30,14 @@ namespace Content.Server.Medical.CrewMonitoring
|
||||
private void OnPacketReceived(EntityUid uid, CrewMonitoringConsoleComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
var payload = args.Data;
|
||||
// check command
|
||||
|
||||
// Check command
|
||||
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||
return;
|
||||
|
||||
if (command != DeviceNetworkConstants.CmdUpdatedState)
|
||||
return;
|
||||
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_STATUS_COLLECTION, out Dictionary<string, SuitSensorStatus>? sensorStatus))
|
||||
return;
|
||||
|
||||
@@ -57,9 +61,14 @@ namespace Content.Server.Medical.CrewMonitoring
|
||||
if (!_uiSystem.TryGetUi(uid, CrewMonitoringUIKey.Key, out var bui))
|
||||
return;
|
||||
|
||||
// update all sensors info
|
||||
// The grid must have a NavMapComponent to visualize the map in the UI
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (xform.GridUid != null)
|
||||
EnsureComp<NavMapComponent>(xform.GridUid.Value);
|
||||
|
||||
// Update all sensors info
|
||||
var allSensors = component.ConnectedSensors.Values.ToList();
|
||||
_uiSystem.SetUiState(bui, new CrewMonitoringState(allSensors, component.Snap, component.Precision));
|
||||
}
|
||||
_uiSystem.SetUiState(bui, new CrewMonitoringState(allSensors));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Medical.SuitSensors
|
||||
{
|
||||
namespace Content.Server.Medical.SuitSensors;
|
||||
|
||||
/// <summary>
|
||||
/// Tracking device, embedded in almost all uniforms and jumpsuits.
|
||||
/// If enabled, will report to crew monitoring console owners position and status.
|
||||
@@ -73,4 +73,3 @@ namespace Content.Server.Medical.SuitSensors
|
||||
[DataField("server")]
|
||||
public string? ConnectedServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Medical.SuitSensors
|
||||
{
|
||||
namespace Content.Server.Medical.SuitSensors;
|
||||
|
||||
public sealed class SuitSensorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -302,12 +302,20 @@ namespace Content.Server.Medical.SuitSensors
|
||||
// try to get mobs id from ID slot
|
||||
var userName = Loc.GetString("suit-sensor-component-unknown-name");
|
||||
var userJob = Loc.GetString("suit-sensor-component-unknown-job");
|
||||
var userJobIcon = "JobIconNoId";
|
||||
var userJobDepartments = new List<string>();
|
||||
|
||||
if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card))
|
||||
{
|
||||
if (card.Comp.FullName != null)
|
||||
userName = card.Comp.FullName;
|
||||
if (card.Comp.JobTitle != null)
|
||||
userJob = card.Comp.JobTitle;
|
||||
if (card.Comp.JobIcon != null)
|
||||
userJobIcon = card.Comp.JobIcon;
|
||||
|
||||
foreach (var department in card.Comp.JobDepartments)
|
||||
userJobDepartments.Add(Loc.GetString(department));
|
||||
}
|
||||
|
||||
// get health mob state
|
||||
@@ -321,7 +329,7 @@ namespace Content.Server.Medical.SuitSensors
|
||||
totalDamage = damageable.TotalDamage.Int();
|
||||
|
||||
// finally, form suit sensor status
|
||||
var status = new SuitSensorStatus(GetNetEntity(uid), userName, userJob);
|
||||
var status = new SuitSensorStatus(GetNetEntity(uid), userName, userJob, userJobIcon, userJobDepartments);
|
||||
switch (sensor.Mode)
|
||||
{
|
||||
case SuitSensorMode.SensorBinary:
|
||||
@@ -370,6 +378,8 @@ namespace Content.Server.Medical.SuitSensors
|
||||
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
|
||||
[SuitSensorConstants.NET_NAME] = status.Name,
|
||||
[SuitSensorConstants.NET_JOB] = status.Job,
|
||||
[SuitSensorConstants.NET_JOB_ICON] = status.JobIcon,
|
||||
[SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments,
|
||||
[SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive,
|
||||
[SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid,
|
||||
};
|
||||
@@ -379,7 +389,6 @@ namespace Content.Server.Medical.SuitSensors
|
||||
if (status.Coordinates != null)
|
||||
payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates);
|
||||
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -397,6 +406,8 @@ namespace Content.Server.Medical.SuitSensors
|
||||
// check name, job and alive
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List<string>? jobDepartments)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null;
|
||||
|
||||
@@ -404,7 +415,7 @@ namespace Content.Server.Medical.SuitSensors
|
||||
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
|
||||
payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords);
|
||||
|
||||
var status = new SuitSensorStatus(suitSensorUid, name, job)
|
||||
var status = new SuitSensorStatus(suitSensorUid, name, job, jobIcon, jobDepartments)
|
||||
{
|
||||
IsAlive = isAlive.Value,
|
||||
TotalDamage = totalDamage,
|
||||
@@ -413,4 +424,3 @@ namespace Content.Server.Medical.SuitSensors
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Access.Components
|
||||
{
|
||||
namespace Content.Shared.Access.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState]
|
||||
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)]
|
||||
@@ -28,5 +28,10 @@ namespace Content.Shared.Access.Components
|
||||
[AutoNetworkedField]
|
||||
public string JobIcon = "JobIconUnknown";
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// The unlocalized names of the departments associated with the job
|
||||
/// </summary>
|
||||
[DataField("jobDepartments")]
|
||||
[AutoNetworkedField]
|
||||
public List<LocId> JobDepartments = new();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Medical.CrewMonitoring
|
||||
{
|
||||
namespace Content.Shared.Medical.CrewMonitoring;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum CrewMonitoringUIKey
|
||||
{
|
||||
@@ -13,15 +13,9 @@ namespace Content.Shared.Medical.CrewMonitoring
|
||||
public sealed class CrewMonitoringState : BoundUserInterfaceState
|
||||
{
|
||||
public List<SuitSensorStatus> Sensors;
|
||||
public readonly bool Snap;
|
||||
public readonly float Precision;
|
||||
|
||||
public CrewMonitoringState(List<SuitSensorStatus> sensors, bool snap, float precision)
|
||||
public CrewMonitoringState(List<SuitSensorStatus> sensors)
|
||||
{
|
||||
Sensors = sensors;
|
||||
Snap = snap;
|
||||
Precision = precision;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Medical.SuitSensor
|
||||
{
|
||||
namespace Content.Shared.Medical.SuitSensor;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SuitSensorStatus
|
||||
{
|
||||
public SuitSensorStatus(NetEntity suitSensorUid, string name, string job)
|
||||
public SuitSensorStatus(NetEntity suitSensorUid, string name, string job, string jobIcon, List<string> jobDepartments)
|
||||
{
|
||||
SuitSensorUid = suitSensorUid;
|
||||
Name = name;
|
||||
Job = job;
|
||||
JobIcon = jobIcon;
|
||||
JobDepartments = jobDepartments;
|
||||
}
|
||||
|
||||
public TimeSpan Timestamp;
|
||||
public NetEntity SuitSensorUid;
|
||||
public string Name;
|
||||
public string Job;
|
||||
public string JobIcon;
|
||||
public List<string> JobDepartments;
|
||||
public bool IsAlive;
|
||||
public int? TotalDamage;
|
||||
public NetCoordinates? Coordinates;
|
||||
@@ -50,6 +54,8 @@ namespace Content.Shared.Medical.SuitSensor
|
||||
{
|
||||
public const string NET_NAME = "name";
|
||||
public const string NET_JOB = "job";
|
||||
public const string NET_JOB_ICON = "jobIcon";
|
||||
public const string NET_JOB_DEPARTMENTS = "jobDepartments";
|
||||
public const string NET_IS_ALIVE = "alive";
|
||||
public const string NET_TOTAL_DAMAGE = "vitals";
|
||||
public const string NET_COORDINATES = "coords";
|
||||
@@ -58,4 +64,3 @@ namespace Content.Shared.Medical.SuitSensor
|
||||
///Used by the CrewMonitoringServerSystem to send the status of all connected suit sensors to each crew monitor
|
||||
public const string NET_STATUS_COLLECTION = "suit-status-collection";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## UI
|
||||
|
||||
crew-monitoring-user-interface-title = Crew Monitoring
|
||||
crew-monitoring-user-interface-title = Crew Monitoring Console
|
||||
|
||||
crew-monitoring-user-interface-name = Name
|
||||
crew-monitoring-user-interface-job = Job
|
||||
@@ -12,3 +12,8 @@ crew-monitoring-user-interface-dead = Dead
|
||||
crew-monitoring-user-interface-no-info = N/A
|
||||
|
||||
crew-monitoring-user-interface-no-server = Server not found
|
||||
|
||||
crew-monitoring-user-interface-no-department = Unknown
|
||||
|
||||
crew-monitoring-user-interface-flavor-left = In case of an emergancy, contact station medical staff immediately
|
||||
crew-monitoring-user-interface-flavor-right = v1.7
|
||||
3
Resources/Locale/en-US/ui/navmap.ftl
Normal file
@@ -0,0 +1,3 @@
|
||||
navmap-zoom = Zoom: {$value}%
|
||||
navmap-recenter = Recenter
|
||||
navmap-toggle-beacons = Show departments
|
||||
|
After Width: | Height: | Size: 615 B |
|
After Width: | Height: | Size: 659 B |
|
After Width: | Height: | Size: 615 B |
|
After Width: | Height: | Size: 614 B |
|
After Width: | Height: | Size: 623 B |
|
After Width: | Height: | Size: 611 B |
|
After Width: | Height: | Size: 617 B |
|
After Width: | Height: | Size: 617 B |
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Created by chromiumboy, derived from https://github.com/tgstation/tgstation/commits/50689f89a40e5e7a2732a0c5fb38c787b69f7d28/icons/hud/screen_gen.dmi, ",
|
||||
"size": {
|
||||
"x": 24,
|
||||
"y": 8
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "alive"
|
||||
},
|
||||
{
|
||||
"name": "dead"
|
||||
},
|
||||
{
|
||||
"name": "health0"
|
||||
},
|
||||
{
|
||||
"name": "health1"
|
||||
},
|
||||
{
|
||||
"name": "health2"
|
||||
},
|
||||
{
|
||||
"name": "health3"
|
||||
},
|
||||
{
|
||||
"name": "health4"
|
||||
},
|
||||
{
|
||||
"name": "critical",
|
||||
"delays": [
|
||||
[
|
||||
0.35,
|
||||
0.35
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/Interface/NavMap/beveled_circle.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |