Crew monitor revisit (#22240)
@@ -1,53 +1,56 @@
|
|||||||
using Content.Shared.Medical.CrewMonitoring;
|
using Content.Shared.Medical.CrewMonitoring;
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Medical.CrewMonitoring
|
namespace Content.Client.Medical.CrewMonitoring;
|
||||||
|
|
||||||
|
public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
|
[ViewVariables]
|
||||||
|
private CrewMonitoringWindow? _menu;
|
||||||
|
|
||||||
|
public CrewMonitoringBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
[ViewVariables]
|
}
|
||||||
private CrewMonitoringWindow? _menu;
|
|
||||||
|
|
||||||
public CrewMonitoringBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
protected override void Open()
|
||||||
|
{
|
||||||
|
EntityUid? gridUid = null;
|
||||||
|
string stationName = string.Empty;
|
||||||
|
|
||||||
|
if (EntMan.TryGetComponent<TransformComponent>(Owner, out var xform))
|
||||||
{
|
{
|
||||||
}
|
gridUid = xform.GridUid;
|
||||||
|
|
||||||
protected override void Open()
|
if (EntMan.TryGetComponent<MetaDataComponent>(gridUid, out var metaData))
|
||||||
{
|
|
||||||
EntityUid? gridUid = null;
|
|
||||||
|
|
||||||
if (EntMan.TryGetComponent<TransformComponent>(Owner, out var xform))
|
|
||||||
{
|
{
|
||||||
gridUid = xform.GridUid;
|
stationName = metaData.EntityName;
|
||||||
}
|
|
||||||
|
|
||||||
_menu = new CrewMonitoringWindow(gridUid);
|
|
||||||
|
|
||||||
_menu.OpenCentered();
|
|
||||||
_menu.OnClose += Close;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateState(BoundUserInterfaceState state)
|
|
||||||
{
|
|
||||||
base.UpdateState(state);
|
|
||||||
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case CrewMonitoringState st:
|
|
||||||
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
|
|
||||||
|
|
||||||
_menu?.ShowSensors(st.Sensors, xform?.Coordinates, st.Snap, st.Precision);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
_menu = new CrewMonitoringWindow(stationName, gridUid);
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
if (!disposing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_menu?.Dispose();
|
_menu.OpenCentered();
|
||||||
|
_menu.OnClose += Close;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case CrewMonitoringState st:
|
||||||
|
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
|
||||||
|
_menu?.ShowSensors(st.Sensors, Owner, xform?.Coordinates);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_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,39 +1,49 @@
|
|||||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
<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"
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
Title="{Loc 'crew-monitoring-user-interface-title'}"
|
Title="{Loc 'crew-monitoring-user-interface-title'}"
|
||||||
SetSize="1130 700"
|
SetSize="1200 700"
|
||||||
MinSize="1130 700">
|
MinSize="1200 700">
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Vertical">
|
||||||
<ScrollContainer HorizontalExpand="True"
|
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
|
||||||
VerticalExpand="True"
|
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20"/>
|
||||||
Margin="8, 8, 8, 8">
|
<BoxContainer Orientation="Vertical">
|
||||||
<GridContainer Name="SensorsTable"
|
<controls:StripeBack>
|
||||||
HorizontalExpand="True"
|
<PanelContainer>
|
||||||
VerticalExpand="True"
|
<Label Name="StationName" Text="Unknown station" Align="Center" />
|
||||||
HSeparationOverride="5"
|
</PanelContainer>
|
||||||
VSeparationOverride="20"
|
</controls:StripeBack>
|
||||||
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"/>
|
|
||||||
|
|
||||||
<!-- Table rows are filled by code -->
|
<ScrollContainer Name="SensorScroller"
|
||||||
</GridContainer>
|
VerticalExpand="True"
|
||||||
<Label Name="NoServerLabel"
|
SetWidth="520"
|
||||||
Text="{Loc 'crew-monitoring-user-interface-no-server'}"
|
Margin="8, 8, 8, 8">
|
||||||
StyleClasses="LabelHeading"
|
<BoxContainer Name="SensorsTable"
|
||||||
FontColorOverride="Red"
|
Orientation="Vertical"
|
||||||
HorizontalAlignment="Center"
|
HorizontalExpand="True"
|
||||||
Visible="false"/>
|
Margin="0 0 10 0">
|
||||||
</ScrollContainer>
|
<!-- Table rows are filled by code -->
|
||||||
<ui:NavMapControl Name="NavMap"
|
</BoxContainer>
|
||||||
Margin="5 5"/>
|
<Label Name="NoServerLabel"
|
||||||
|
Text="{Loc 'crew-monitoring-user-interface-no-server'}"
|
||||||
|
StyleClasses="LabelHeading"
|
||||||
|
FontColorOverride="Red"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Visible="false"/>
|
||||||
|
</ScrollContainer>
|
||||||
|
</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>
|
</BoxContainer>
|
||||||
</controls:FancyWindow>
|
</controls:FancyWindow>
|
||||||
|
|||||||
@@ -1,275 +1,437 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Content.Client.Pinpointer.UI;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared.Medical.SuitSensor;
|
using Content.Shared.Medical.SuitSensor;
|
||||||
|
using Content.Shared.StatusIcon;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||||
|
|
||||||
namespace Content.Client.Medical.CrewMonitoring
|
namespace Content.Client.Medical.CrewMonitoring;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||||
{
|
{
|
||||||
[GenerateTypedNameReferences]
|
private List<Control> _rowsContent = new();
|
||||||
public sealed partial class CrewMonitoringWindow : FancyWindow
|
private readonly IEntityManager _entManager;
|
||||||
|
private readonly IPrototypeManager _prototypeManager;
|
||||||
|
private readonly SpriteSystem _spriteSystem;
|
||||||
|
|
||||||
|
private NetEntity? _trackedEntity;
|
||||||
|
private bool _tryToScrollToListFocus;
|
||||||
|
private Texture? _blipTexture;
|
||||||
|
|
||||||
|
public CrewMonitoringWindow(string stationName, EntityUid? mapUid)
|
||||||
{
|
{
|
||||||
private List<Control> _rowsContent = new();
|
RobustXamlLoader.Load(this);
|
||||||
private List<(DirectionIcon Icon, Vector2 Position)> _directionIcons = new();
|
|
||||||
private readonly IEntityManager _entManager;
|
|
||||||
private readonly IEyeManager _eye;
|
|
||||||
private EntityUid? _stationUid;
|
|
||||||
private CrewMonitoringButton? _trackedButton;
|
|
||||||
|
|
||||||
public static int IconSize = 16; // XAML has a `VSeparationOverride` of 20 for each row.
|
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
_prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||||
|
|
||||||
public CrewMonitoringWindow(EntityUid? mapUid)
|
_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;
|
||||||
|
|
||||||
|
StationName.AddStyleClass("LabelBig");
|
||||||
|
StationName.Text = stationName;
|
||||||
|
|
||||||
|
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
|
||||||
|
NavMap.ForceNavMapUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
|
if (_tryToScrollToListFocus)
|
||||||
|
TryToScrollToFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowSensors(List<SuitSensorStatus> sensors, EntityUid monitor, EntityCoordinates? monitorCoords)
|
||||||
|
{
|
||||||
|
ClearOutDatedData();
|
||||||
|
|
||||||
|
// No server label
|
||||||
|
if (sensors.Count == 0)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
NoServerLabel.Visible = true;
|
||||||
_eye = IoCManager.Resolve<IEyeManager>();
|
return;
|
||||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
_stationUid = mapUid;
|
|
||||||
|
|
||||||
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)
|
NoServerLabel.Visible = false;
|
||||||
|
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
ClearAllSensors();
|
var departmentSensors = orderedSensors.Where(d => d.JobDepartments.Contains(department));
|
||||||
|
|
||||||
var monitorCoordsInStationSpace = _stationUid != null ? monitorCoords?.WithEntityId(_stationUid.Value, _entManager).Position : null;
|
if (departmentSensors == null || !departmentSensors.Any())
|
||||||
|
continue;
|
||||||
|
|
||||||
// TODO scroll container
|
foreach (var sensor in departmentSensors)
|
||||||
// TODO filter by name & occupation
|
assignedSensors.Add(sensor);
|
||||||
// TODO make each row a xaml-control. Get rid of some of this c# control creation.
|
|
||||||
if (stSensors.Count == 0)
|
if (SensorsTable.ChildCount > 0)
|
||||||
{
|
{
|
||||||
NoServerLabel.Visible = true;
|
var spacer = new Control()
|
||||||
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,
|
SetHeight = 20,
|
||||||
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
|
SensorsTable.AddChild(spacer);
|
||||||
// format: JobName
|
_rowsContent.Add(spacer);
|
||||||
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)
|
var deparmentLabel = new RichTextLabel()
|
||||||
NavMap.TrackedCoordinates.Add(monitorCoords.Value, (true, StyleNano.PointMagenta));
|
{
|
||||||
|
Margin = new Thickness(10, 0),
|
||||||
|
HorizontalExpand = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
deparmentLabel.SetMessage(department);
|
||||||
|
deparmentLabel.StyleClasses.Add(StyleNano.StyleClassTooltipActionDescription);
|
||||||
|
|
||||||
|
SensorsTable.AddChild(deparmentLabel);
|
||||||
|
_rowsContent.Add(deparmentLabel);
|
||||||
|
|
||||||
|
PopulateDepartmentList(departmentSensors);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BoxContainer GetPositionBox(SuitSensorStatus sensor, Vector2 monitorCoordsInStationSpace, bool snap, float precision)
|
// Account for any non-station users
|
||||||
|
var remainingSensors = orderedSensors.Except(assignedSensors);
|
||||||
|
|
||||||
|
if (remainingSensors.Any())
|
||||||
{
|
{
|
||||||
EntityCoordinates? coordinates = _entManager.GetCoordinates(sensor.Coordinates);
|
var spacer = new Control()
|
||||||
var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal };
|
|
||||||
|
|
||||||
if (coordinates == null || _stationUid == null)
|
|
||||||
{
|
{
|
||||||
var dirIcon = new DirectionIcon()
|
SetHeight = 20,
|
||||||
{
|
};
|
||||||
SetSize = new Vector2(IconSize, IconSize),
|
|
||||||
Margin = new(0, 0, 4, 0)
|
SensorsTable.AddChild(spacer);
|
||||||
};
|
_rowsContent.Add(spacer);
|
||||||
box.AddChild(dirIcon);
|
|
||||||
box.AddChild(new Label() { Text = Loc.GetString("crew-monitoring-user-interface-no-info") });
|
var deparmentLabel = new RichTextLabel()
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
var local = coordinates.Value.WithEntityId(_stationUid.Value, _entManager).Position;
|
Margin = new Thickness(10, 0),
|
||||||
|
HorizontalExpand = true,
|
||||||
|
};
|
||||||
|
|
||||||
var displayPos = local.Floored();
|
deparmentLabel.SetMessage(Loc.GetString("crew-monitoring-user-interface-no-department"));
|
||||||
var dirIcon = new DirectionIcon(snap, precision)
|
deparmentLabel.StyleClasses.Add(StyleNano.StyleClassTooltipActionDescription);
|
||||||
{
|
|
||||||
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;
|
SensorsTable.AddChild(deparmentLabel);
|
||||||
|
_rowsContent.Add(deparmentLabel);
|
||||||
|
|
||||||
|
PopulateDepartmentList(remainingSensors);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FrameUpdate(FrameEventArgs args)
|
// Show monitor on nav map
|
||||||
|
if (monitorCoords != null && _blipTexture != null)
|
||||||
{
|
{
|
||||||
// the window is separate from any specific viewport, so there is no real way to get an eye-rotation without
|
NavMap.TrackedEntities[_entManager.GetNetEntity(monitor)] = new NavMapBlip(monitorCoords.Value, _blipTexture, Color.Cyan, true, false);
|
||||||
// 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?)
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (icon, pos) in _directionIcons)
|
|
||||||
{
|
|
||||||
icon.UpdateDirection(pos, offsetAngle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearAllSensors()
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Convert from null to regular int
|
|
||||||
int damage;
|
|
||||||
if (totalDamage == null) return;
|
|
||||||
else damage = (int) totalDamage;
|
|
||||||
|
|
||||||
if (damage <= 0)
|
|
||||||
{
|
|
||||||
label.FontColorOverride = startColor;
|
|
||||||
}
|
|
||||||
else if (damage >= 200)
|
|
||||||
{
|
|
||||||
label.FontColorOverride = endColor;
|
|
||||||
}
|
|
||||||
else if (damage >= 0 && damage <= 100)
|
|
||||||
{
|
|
||||||
label.FontColorOverride = GetColorLerp(startColor, critColor, damage);
|
|
||||||
}
|
|
||||||
else if (damage >= 100 && damage <= 200)
|
|
||||||
{
|
|
||||||
//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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color GetColorLerp(Color startColor, Color endColor, int damage)
|
|
||||||
{
|
|
||||||
//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);
|
|
||||||
|
|
||||||
return new Color(r, g, b, a);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CrewMonitoringButton : Button
|
private void PopulateDepartmentList(IEnumerable<SuitSensorStatus> departmentSensors)
|
||||||
{
|
{
|
||||||
public int IndexInTable;
|
// Populate departments
|
||||||
public EntityUid? SuitSensorUid;
|
foreach (var sensor in departmentSensors)
|
||||||
public EntityCoordinates? Coordinates;
|
{
|
||||||
|
var coordinates = _entManager.GetCoordinates(sensor.Coordinates);
|
||||||
|
|
||||||
|
// 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 NetEntity SuitSensorUid;
|
||||||
|
public EntityCoordinates? Coordinates;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,40 +1,66 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared.Pinpointer;
|
using Content.Shared.Pinpointer;
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Collision.Shapes;
|
using Robust.Shared.Physics.Collision.Shapes;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using System.Numerics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Content.Client.Pinpointer.UI;
|
namespace Content.Client.Pinpointer.UI;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Displays the nav map data of the specified grid.
|
/// Displays the nav map data of the specified grid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class NavMapControl : MapGridControl
|
[UsedImplicitly, Virtual]
|
||||||
|
public partial class NavMapControl : MapGridControl
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
private SharedTransformSystem _transform;
|
private readonly SharedTransformSystem _transformSystem = default!;
|
||||||
|
|
||||||
public EntityUid? MapUid;
|
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 Vector2 _offset;
|
||||||
private bool _draggin;
|
private bool _draggin;
|
||||||
private bool _recentering = false;
|
private bool _recentering = false;
|
||||||
private readonly float _recenterMinimum = 0.05f;
|
|
||||||
private readonly Font _font;
|
private readonly Font _font;
|
||||||
private static readonly Color TileColor = new(30, 67, 30);
|
private float _updateTimer = 0.25f;
|
||||||
private static readonly Color BeaconColor = Color.FromSrgb(TileColor.WithAlpha(0.8f));
|
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
|
// TODO: https://github.com/space-wizards/RobustToolbox/issues/3818
|
||||||
private readonly Label _zoom = new()
|
private readonly Label _zoom = new()
|
||||||
@@ -45,20 +71,30 @@ public sealed class NavMapControl : MapGridControl
|
|||||||
|
|
||||||
private readonly Button _recenter = new()
|
private readonly Button _recenter = new()
|
||||||
{
|
{
|
||||||
Text = "Recentre",
|
Text = Loc.GetString("navmap-recenter"),
|
||||||
VerticalAlignment = VAlignment.Top,
|
VerticalAlignment = VAlignment.Top,
|
||||||
HorizontalAlignment = HAlignment.Right,
|
HorizontalAlignment = HAlignment.Right,
|
||||||
Margin = new Thickness(8f, 4f),
|
Margin = new Thickness(8f, 4f),
|
||||||
Disabled = true,
|
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)
|
public NavMapControl() : base(8f, 128f, 48f)
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
_transform = _entManager.System<SharedTransformSystem>();
|
|
||||||
var cache = IoCManager.Resolve<IResourceCache>();
|
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;
|
RectClipContent = true;
|
||||||
HorizontalExpand = true;
|
HorizontalExpand = true;
|
||||||
@@ -75,6 +111,7 @@ public sealed class NavMapControl : MapGridControl
|
|||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
_zoom,
|
_zoom,
|
||||||
|
_beacons,
|
||||||
_recenter,
|
_recenter,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -101,14 +138,28 @@ public sealed class NavMapControl : MapGridControl
|
|||||||
{
|
{
|
||||||
_recentering = true;
|
_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)
|
public void CenterToCoordinates(EntityCoordinates coordinates)
|
||||||
{
|
{
|
||||||
if (_entManager.TryGetComponent<PhysicsComponent>(MapUid, out var physics))
|
if (_physics != null)
|
||||||
{
|
_offset = new Vector2(coordinates.X, coordinates.Y) - _physics.LocalCenter;
|
||||||
_offset = new Vector2(coordinates.X, coordinates.Y) - physics.LocalCenter;
|
|
||||||
}
|
|
||||||
_recenter.Disabled = false;
|
_recenter.Disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,18 +168,62 @@ public sealed class NavMapControl : MapGridControl
|
|||||||
base.KeyBindDown(args);
|
base.KeyBindDown(args);
|
||||||
|
|
||||||
if (args.Function == EngineKeyFunctions.Use)
|
if (args.Function == EngineKeyFunctions.Use)
|
||||||
{
|
|
||||||
_draggin = true;
|
_draggin = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||||
{
|
{
|
||||||
base.KeyBindUp(args);
|
base.KeyBindUp(args);
|
||||||
|
|
||||||
|
if (TrackedEntitySelectedAction == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (args.Function == EngineKeyFunctions.Use)
|
if (args.Function == EngineKeyFunctions.Use)
|
||||||
{
|
{
|
||||||
_draggin = false;
|
_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;
|
_offset -= new Vector2(args.Relative.X, -args.Relative.Y) / MidPoint * WorldRange;
|
||||||
|
|
||||||
if (_offset != Vector2.Zero)
|
if (_offset != Vector2.Zero)
|
||||||
{
|
|
||||||
_recenter.Disabled = false;
|
_recenter.Disabled = false;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
_recenter.Disabled = true;
|
_recenter.Disabled = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(DrawingHandleScreen handle)
|
protected override void Draw(DrawingHandleScreen handle)
|
||||||
{
|
{
|
||||||
base.Draw(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)
|
if (_recentering)
|
||||||
{
|
{
|
||||||
var frameTime = Timing.FrameTime;
|
var frameTime = Timing.FrameTime;
|
||||||
var diff = _offset * (float) frameTime.TotalSeconds;
|
var diff = _offset * (float) frameTime.TotalSeconds;
|
||||||
|
|
||||||
if (_offset.LengthSquared() < _recenterMinimum)
|
if (_offset.LengthSquared() < RecenterMinimum)
|
||||||
{
|
{
|
||||||
_offset = Vector2.Zero;
|
_offset = Vector2.Zero;
|
||||||
_recentering = false;
|
_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) ||
|
if (_navMap == null || _xform == null)
|
||||||
!_entManager.TryGetComponent<TransformComponent>(MapUid, out var xform) ||
|
|
||||||
!_entManager.TryGetComponent<MapGridComponent>(MapUid, out var grid))
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var offset = _offset;
|
var offset = _offset;
|
||||||
var lineColor = new Color(102, 217, 102);
|
|
||||||
|
|
||||||
if (_entManager.TryGetComponent<PhysicsComponent>(MapUid, out var physics))
|
if (_physics != null)
|
||||||
{
|
offset += _physics.LocalCenter;
|
||||||
offset += physics.LocalCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw tiles
|
// Draw tiles
|
||||||
if (_entManager.TryGetComponent<FixturesComponent>(MapUid, out var manager))
|
if (_fixtures != null)
|
||||||
{
|
{
|
||||||
Span<Vector2> verts = new Vector2[8];
|
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)
|
if (fixture.Shape is not PolygonShape poly)
|
||||||
continue;
|
continue;
|
||||||
@@ -211,157 +304,305 @@ public sealed class NavMapControl : MapGridControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the wall data
|
|
||||||
var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
|
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 (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
||||||
|
|
||||||
if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// TODO: Okay maybe I should just use ushorts lmao...
|
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
||||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
continue;
|
||||||
|
|
||||||
|
foreach (var chunkedLine in chunkedLines)
|
||||||
{
|
{
|
||||||
var value = (int) Math.Pow(2, i);
|
var start = Scale(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
|
||||||
|
var end = Scale(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
|
||||||
|
|
||||||
var mask = chunk.TileData & value;
|
walls.Add(start);
|
||||||
|
walls.Add(end);
|
||||||
if (mask == 0x0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Alright now we'll work out our edges
|
|
||||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
|
||||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize - offset;
|
|
||||||
var position = new Vector2(tile.X, -tile.Y);
|
|
||||||
NavMapChunk? neighborChunk;
|
|
||||||
bool neighbor;
|
|
||||||
|
|
||||||
// North edge
|
|
||||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
|
||||||
{
|
|
||||||
neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
|
||||||
(neighborChunk.TileData &
|
|
||||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
|
||||||
neighbor = (chunk.TileData & flag) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!neighbor)
|
|
||||||
{
|
|
||||||
handle.DrawLine(Scale(position + new Vector2(0f, -grid.TileSize)), Scale(position + tileSize), lineColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// East edge
|
|
||||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
|
||||||
{
|
|
||||||
neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
|
||||||
(neighborChunk.TileData &
|
|
||||||
SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
|
||||||
neighbor = (chunk.TileData & flag) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!neighbor)
|
|
||||||
{
|
|
||||||
handle.DrawLine(Scale(position + tileSize), Scale(position + new Vector2(grid.TileSize, 0f)), lineColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// South edge
|
|
||||||
if (relativeTile.Y == 0)
|
|
||||||
{
|
|
||||||
neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) &&
|
|
||||||
(neighborChunk.TileData &
|
|
||||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1));
|
|
||||||
neighbor = (chunk.TileData & flag) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!neighbor)
|
|
||||||
{
|
|
||||||
handle.DrawLine(Scale(position + new Vector2(grid.TileSize, 0f)), Scale(position), lineColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// West edge
|
|
||||||
if (relativeTile.X == 0)
|
|
||||||
{
|
|
||||||
neighbor = navMap.Chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) &&
|
|
||||||
(neighborChunk.TileData &
|
|
||||||
SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0));
|
|
||||||
neighbor = (chunk.TileData & flag) != 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!neighbor)
|
|
||||||
{
|
|
||||||
handle.DrawLine(Scale(position), Scale(position + new Vector2(0f, -grid.TileSize)), lineColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a diagonal line for interiors.
|
|
||||||
handle.DrawLine(Scale(position + new Vector2(0f, -grid.TileSize)), Scale(position + new Vector2(grid.TileSize, 0f)), lineColor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 curTime = Timing.RealTime;
|
||||||
var blinkFrequency = 1f / 1f;
|
var blinkFrequency = 1f / 1f;
|
||||||
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
|
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
|
||||||
|
|
||||||
|
// Tracked coordinates (simple dot, legacy)
|
||||||
foreach (var (coord, value) in TrackedCoordinates)
|
foreach (var (coord, value) in TrackedCoordinates)
|
||||||
{
|
{
|
||||||
if (lit && value.Visible)
|
if (lit && value.Visible)
|
||||||
{
|
{
|
||||||
var mapPos = coord.ToMap(_entManager);
|
var mapPos = coord.ToMap(_entManager, _transformSystem);
|
||||||
|
|
||||||
if (mapPos.MapId != MapId.Nullspace)
|
if (mapPos.MapId != MapId.Nullspace)
|
||||||
{
|
{
|
||||||
var position = xform.InvWorldMatrix.Transform(mapPos.Position) - offset;
|
var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
|
||||||
position = Scale(new Vector2(position.X, -position.Y));
|
position = Scale(new Vector2(position.X, -position.Y));
|
||||||
|
|
||||||
handle.DrawCircle(position, MinimapScale / 2f, value.Color);
|
handle.DrawCircle(position, float.Sqrt(MinimapScale) * 2f, value.Color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beacons
|
// Tracked entities (can use a supplied sprite as a marker instead; should probably just replace TrackedCoordinates with this eventually)
|
||||||
var labelOffset = new Vector2(0.5f, 0.5f) * MinimapScale;
|
var iconVertexUVs = new Dictionary<(Texture, Color), ValueList<DrawVertexUV2D>>();
|
||||||
var rectBuffer = new Vector2(5f, 3f);
|
|
||||||
|
|
||||||
foreach (var beacon in navMap.Beacons)
|
foreach (var blip in TrackedEntities.Values)
|
||||||
{
|
{
|
||||||
var position = beacon.Position - offset;
|
if (blip.Blinks && !lit)
|
||||||
|
continue;
|
||||||
|
|
||||||
position = Scale(position with { Y = -position.Y });
|
if (blip.Texture == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
handle.DrawCircle(position, MinimapScale / 2f, beacon.Color);
|
if (!iconVertexUVs.TryGetValue((blip.Texture, blip.Color), out var vertexUVs))
|
||||||
var textDimensions = handle.GetDimensions(_font, beacon.Text, 1f);
|
vertexUVs = new();
|
||||||
|
|
||||||
var labelPosition = position + labelOffset;
|
var mapPos = blip.Coordinates.ToMap(_entManager, _transformSystem);
|
||||||
handle.DrawRect(new UIBox2(labelPosition, labelPosition + textDimensions + rectBuffer * 2), BeaconColor);
|
|
||||||
handle.DrawString(_font, labelPosition + rectBuffer, beacon.Text, beacon.Color);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 Scale(Vector2 position)
|
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++)
|
||||||
|
{
|
||||||
|
var value = (int) Math.Pow(2, i);
|
||||||
|
|
||||||
|
var mask = chunk.TileData & value;
|
||||||
|
|
||||||
|
if (mask == 0x0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Alright now we'll work out our edges
|
||||||
|
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||||
|
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
|
||||||
|
var position = new Vector2(tile.X, -tile.Y);
|
||||||
|
NavMapChunk? neighborChunk;
|
||||||
|
bool neighbor;
|
||||||
|
|
||||||
|
// North edge
|
||||||
|
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
||||||
|
{
|
||||||
|
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
||||||
|
(neighborChunk.TileData &
|
||||||
|
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
||||||
|
neighbor = (chunk.TileData & flag) != 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!neighbor)
|
||||||
|
{
|
||||||
|
// 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 = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
||||||
|
(neighborChunk.TileData &
|
||||||
|
SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
||||||
|
neighbor = (chunk.TileData & flag) != 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!neighbor)
|
||||||
|
{
|
||||||
|
// 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 = chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) &&
|
||||||
|
(neighborChunk.TileData &
|
||||||
|
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1));
|
||||||
|
neighbor = (chunk.TileData & flag) != 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!neighbor)
|
||||||
|
{
|
||||||
|
// Add points
|
||||||
|
list.Add(new NavMapLine(position + new Vector2(grid.TileSize, 0f), position));
|
||||||
|
}
|
||||||
|
|
||||||
|
// West edge
|
||||||
|
if (relativeTile.X == 0)
|
||||||
|
{
|
||||||
|
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) &&
|
||||||
|
(neighborChunk.TileData &
|
||||||
|
SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0));
|
||||||
|
neighbor = (chunk.TileData & flag) != 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!neighbor)
|
||||||
|
{
|
||||||
|
// Add point
|
||||||
|
list.Add(new NavMapLine(position, position + new Vector2(0f, -grid.TileSize)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a diagonal line for interiors.
|
||||||
|
list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedOutput.Add(chunkOrigin, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2 Scale(Vector2 position)
|
||||||
{
|
{
|
||||||
return position * MinimapScale + MidpointVector;
|
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;
|
Title = metadata.EntityName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NavMapScreen.ForceNavMapUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Access.Components
|
namespace Content.Server.Access.Components;
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class PresetIdCardComponent : Component
|
|
||||||
{
|
|
||||||
[DataField("job")]
|
|
||||||
public ProtoId<JobPrototype>? JobName;
|
|
||||||
|
|
||||||
[DataField("name")]
|
[RegisterComponent]
|
||||||
public string? IdName;
|
public sealed partial class PresetIdCardComponent : Component
|
||||||
}
|
{
|
||||||
|
[DataField("job")]
|
||||||
|
public ProtoId<JobPrototype>? JobName;
|
||||||
|
|
||||||
|
[DataField("name")]
|
||||||
|
public string? IdName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,200 +7,216 @@ using Content.Shared.Access.Components;
|
|||||||
using Content.Shared.Access.Systems;
|
using Content.Shared.Access.Systems;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.StatusIcon;
|
using Content.Shared.StatusIcon;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.Access.Systems
|
namespace Content.Server.Access.Systems;
|
||||||
|
|
||||||
|
public sealed class IdCardSystem : SharedIdCardSystem
|
||||||
{
|
{
|
||||||
public sealed class IdCardSystem : SharedIdCardSystem
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
base.Initialize();
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
SubscribeLocalEvent<IdCardComponent, MapInitEvent>(OnMapInit);
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
SubscribeLocalEvent<IdCardComponent, BeingMicrowavedEvent>(OnMicrowaved);
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
}
|
||||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
private void OnMapInit(EntityUid uid, IdCardComponent id, MapInitEvent args)
|
||||||
{
|
{
|
||||||
base.Initialize();
|
UpdateEntityName(uid, id);
|
||||||
SubscribeLocalEvent<IdCardComponent, MapInitEvent>(OnMapInit);
|
}
|
||||||
SubscribeLocalEvent<IdCardComponent, BeingMicrowavedEvent>(OnMicrowaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, IdCardComponent id, MapInitEvent args)
|
private void OnMicrowaved(EntityUid uid, IdCardComponent component, BeingMicrowavedEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<AccessComponent>(uid, out var access))
|
||||||
{
|
{
|
||||||
UpdateEntityName(uid, id);
|
float randomPick = _random.NextFloat();
|
||||||
}
|
// if really unlucky, burn card
|
||||||
|
if (randomPick <= 0.15f)
|
||||||
private void OnMicrowaved(EntityUid uid, IdCardComponent component, BeingMicrowavedEvent args)
|
|
||||||
{
|
|
||||||
if (TryComp<AccessComponent>(uid, out var access))
|
|
||||||
{
|
{
|
||||||
float randomPick = _random.NextFloat();
|
TryComp(uid, out TransformComponent? transformComponent);
|
||||||
// if really unlucky, burn card
|
if (transformComponent != null)
|
||||||
if (randomPick <= 0.15f)
|
|
||||||
{
|
{
|
||||||
TryComp(uid, out TransformComponent? transformComponent);
|
_popupSystem.PopupCoordinates(Loc.GetString("id-card-component-microwave-burnt", ("id", uid)),
|
||||||
if (transformComponent != null)
|
transformComponent.Coordinates, PopupType.Medium);
|
||||||
{
|
EntityManager.SpawnEntity("FoodBadRecipe",
|
||||||
_popupSystem.PopupCoordinates(Loc.GetString("id-card-component-microwave-burnt", ("id", uid)),
|
transformComponent.Coordinates);
|
||||||
transformComponent.Coordinates, PopupType.Medium);
|
|
||||||
EntityManager.SpawnEntity("FoodBadRecipe",
|
|
||||||
transformComponent.Coordinates);
|
|
||||||
}
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
|
||||||
$"{ToPrettyString(args.Microwave)} burnt {ToPrettyString(uid):entity}");
|
|
||||||
EntityManager.QueueDeleteEntity(uid);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// If they're unlucky, brick their ID
|
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||||
if (randomPick <= 0.25f)
|
$"{ToPrettyString(args.Microwave)} burnt {ToPrettyString(uid):entity}");
|
||||||
{
|
EntityManager.QueueDeleteEntity(uid);
|
||||||
_popupSystem.PopupEntity(Loc.GetString("id-card-component-microwave-bricked", ("id", uid)), uid);
|
return;
|
||||||
|
}
|
||||||
|
// If they're unlucky, brick their ID
|
||||||
|
if (randomPick <= 0.25f)
|
||||||
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("id-card-component-microwave-bricked", ("id", uid)), uid);
|
||||||
|
|
||||||
access.Tags.Clear();
|
access.Tags.Clear();
|
||||||
Dirty(access);
|
|
||||||
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
|
||||||
$"{ToPrettyString(args.Microwave)} cleared access on {ToPrettyString(uid):entity}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("id-card-component-microwave-safe", ("id", uid)), uid, PopupType.Medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give them a wonderful new access to compensate for everything
|
|
||||||
var random = _random.Pick(_prototypeManager.EnumeratePrototypes<AccessLevelPrototype>().ToArray());
|
|
||||||
|
|
||||||
access.Tags.Add(random.ID);
|
|
||||||
Dirty(access);
|
Dirty(access);
|
||||||
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||||
$"{ToPrettyString(args.Microwave)} added {random.ID} access to {ToPrettyString(uid):entity}");
|
$"{ToPrettyString(args.Microwave)} cleared access on {ToPrettyString(uid):entity}");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to change the job title of a card.
|
|
||||||
/// Returns true/false.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs.
|
|
||||||
/// </remarks>
|
|
||||||
public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? id = null, EntityUid? player = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref id))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(jobTitle))
|
|
||||||
{
|
|
||||||
jobTitle = jobTitle.Trim();
|
|
||||||
|
|
||||||
if (jobTitle.Length > IdCardConsoleComponent.MaxJobTitleLength)
|
|
||||||
jobTitle = jobTitle[..IdCardConsoleComponent.MaxJobTitleLength];
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
jobTitle = null;
|
_popupSystem.PopupEntity(Loc.GetString("id-card-component-microwave-safe", ("id", uid)), uid, PopupType.Medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id.JobTitle == jobTitle)
|
// Give them a wonderful new access to compensate for everything
|
||||||
return true;
|
var random = _random.Pick(_prototypeManager.EnumeratePrototypes<AccessLevelPrototype>().ToArray());
|
||||||
id.JobTitle = jobTitle;
|
|
||||||
Dirty(id);
|
|
||||||
UpdateEntityName(uid, id);
|
|
||||||
|
|
||||||
if (player != null)
|
access.Tags.Add(random.ID);
|
||||||
{
|
Dirty(access);
|
||||||
_adminLogger.Add(LogType.Identity, LogImpact.Low,
|
|
||||||
$"{ToPrettyString(player.Value):player} has changed the job title of {ToPrettyString(uid):entity} to {jobTitle} ");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryChangeJobIcon(EntityUid uid, StatusIconPrototype jobIcon, IdCardComponent? id = null, EntityUid? player = null)
|
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||||
{
|
$"{ToPrettyString(args.Microwave)} added {random.ID} access to {ToPrettyString(uid):entity}");
|
||||||
if (!Resolve(uid, ref id))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id.JobIcon == jobIcon.ID)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
id.JobIcon = jobIcon.ID;
|
|
||||||
Dirty(uid, id);
|
|
||||||
|
|
||||||
if (player != null)
|
|
||||||
{
|
|
||||||
_adminLogger.Add(LogType.Identity, LogImpact.Low,
|
|
||||||
$"{ToPrettyString(player.Value):player} has changed the job icon of {ToPrettyString(uid):entity} to {jobIcon} ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to change the full name of a card.
|
|
||||||
/// Returns true/false.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs.
|
|
||||||
/// </remarks>
|
|
||||||
public bool TryChangeFullName(EntityUid uid, string? fullName, IdCardComponent? id = null, EntityUid? player = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref id))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(fullName))
|
|
||||||
{
|
|
||||||
fullName = fullName.Trim();
|
|
||||||
if (fullName.Length > IdCardConsoleComponent.MaxFullNameLength)
|
|
||||||
fullName = fullName[..IdCardConsoleComponent.MaxFullNameLength];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fullName = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id.FullName == fullName)
|
|
||||||
return true;
|
|
||||||
id.FullName = fullName;
|
|
||||||
Dirty(id);
|
|
||||||
UpdateEntityName(uid, id);
|
|
||||||
|
|
||||||
if (player != null)
|
|
||||||
{
|
|
||||||
_adminLogger.Add(LogType.Identity, LogImpact.Low,
|
|
||||||
$"{ToPrettyString(player.Value):player} has changed the name of {ToPrettyString(uid):entity} to {fullName} ");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Changes the name of the id's owner.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// If either <see cref="FullName"/> or <see cref="JobTitle"/> is empty, it's replaced by placeholders.
|
|
||||||
/// If both are empty, the original entity's name is restored.
|
|
||||||
/// </remarks>
|
|
||||||
private void UpdateEntityName(EntityUid uid, IdCardComponent? id = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})";
|
|
||||||
|
|
||||||
var val = string.IsNullOrWhiteSpace(id.FullName)
|
|
||||||
? Loc.GetString("access-id-card-component-owner-name-job-title-text",
|
|
||||||
("jobSuffix", jobSuffix))
|
|
||||||
: Loc.GetString("access-id-card-component-owner-full-name-job-title-text",
|
|
||||||
("fullName", id.FullName),
|
|
||||||
("jobSuffix", jobSuffix));
|
|
||||||
_metaSystem.SetEntityName(uid, val);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to change the job title of a card.
|
||||||
|
/// Returns true/false.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs.
|
||||||
|
/// </remarks>
|
||||||
|
public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? id = null, EntityUid? player = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(jobTitle))
|
||||||
|
{
|
||||||
|
jobTitle = jobTitle.Trim();
|
||||||
|
|
||||||
|
if (jobTitle.Length > IdCardConsoleComponent.MaxJobTitleLength)
|
||||||
|
jobTitle = jobTitle[..IdCardConsoleComponent.MaxJobTitleLength];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jobTitle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.JobTitle == jobTitle)
|
||||||
|
return true;
|
||||||
|
id.JobTitle = jobTitle;
|
||||||
|
Dirty(id);
|
||||||
|
UpdateEntityName(uid, id);
|
||||||
|
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
_adminLogger.Add(LogType.Identity, LogImpact.Low,
|
||||||
|
$"{ToPrettyString(player.Value):player} has changed the job title of {ToPrettyString(uid):entity} to {jobTitle} ");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryChangeJobIcon(EntityUid uid, StatusIconPrototype jobIcon, IdCardComponent? id = null, EntityUid? player = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref id))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.JobIcon == jobIcon.ID)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
id.JobIcon = jobIcon.ID;
|
||||||
|
Dirty(uid, id);
|
||||||
|
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
_adminLogger.Add(LogType.Identity, LogImpact.Low,
|
||||||
|
$"{ToPrettyString(player.Value):player} has changed the job icon of {ToPrettyString(uid):entity} to {jobIcon} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs.
|
||||||
|
/// </remarks>
|
||||||
|
public bool TryChangeFullName(EntityUid uid, string? fullName, IdCardComponent? id = null, EntityUid? player = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(fullName))
|
||||||
|
{
|
||||||
|
fullName = fullName.Trim();
|
||||||
|
if (fullName.Length > IdCardConsoleComponent.MaxFullNameLength)
|
||||||
|
fullName = fullName[..IdCardConsoleComponent.MaxFullNameLength];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fullName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.FullName == fullName)
|
||||||
|
return true;
|
||||||
|
id.FullName = fullName;
|
||||||
|
Dirty(id);
|
||||||
|
UpdateEntityName(uid, id);
|
||||||
|
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
_adminLogger.Add(LogType.Identity, LogImpact.Low,
|
||||||
|
$"{ToPrettyString(player.Value):player} has changed the name of {ToPrettyString(uid):entity} to {fullName} ");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the name of the id's owner.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If either <see cref="FullName"/> or <see cref="JobTitle"/> is empty, it's replaced by placeholders.
|
||||||
|
/// If both are empty, the original entity's name is restored.
|
||||||
|
/// </remarks>
|
||||||
|
private void UpdateEntityName(EntityUid uid, IdCardComponent? id = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})";
|
||||||
|
|
||||||
|
var val = string.IsNullOrWhiteSpace(id.FullName)
|
||||||
|
? Loc.GetString("access-id-card-component-owner-name-job-title-text",
|
||||||
|
("jobSuffix", jobSuffix))
|
||||||
|
: Loc.GetString("access-id-card-component-owner-full-name-job-title-text",
|
||||||
|
("fullName", id.FullName),
|
||||||
|
("jobSuffix", jobSuffix));
|
||||||
|
_metaSystem.SetEntityName(uid, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,82 +7,82 @@ using Content.Shared.Roles;
|
|||||||
using Content.Shared.StatusIcon;
|
using Content.Shared.StatusIcon;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Access.Systems
|
namespace Content.Server.Access.Systems;
|
||||||
|
|
||||||
|
public sealed class PresetIdCardSystem : EntitySystem
|
||||||
{
|
{
|
||||||
public sealed class PresetIdCardSystem : EntitySystem
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||||
|
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
|
||||||
|
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
SubscribeLocalEvent<PresetIdCardComponent, MapInitEvent>(OnMapInit);
|
||||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
|
||||||
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
|
|
||||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(PlayerJobsAssigned);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerJobsAssigned(RulePlayerJobsAssignedEvent ev)
|
||||||
|
{
|
||||||
|
// Go over all ID cards and make sure they're correctly configured for extended access.
|
||||||
|
|
||||||
|
var query = EntityQueryEnumerator<PresetIdCardComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var card))
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<PresetIdCardComponent, MapInitEvent>(OnMapInit);
|
|
||||||
|
|
||||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(PlayerJobsAssigned);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayerJobsAssigned(RulePlayerJobsAssignedEvent ev)
|
|
||||||
{
|
|
||||||
// Go over all ID cards and make sure they're correctly configured for extended access.
|
|
||||||
|
|
||||||
var query = EntityQueryEnumerator<PresetIdCardComponent>();
|
|
||||||
while (query.MoveNext(out var uid, out var card))
|
|
||||||
{
|
|
||||||
var station = _stationSystem.GetOwningStation(uid);
|
|
||||||
|
|
||||||
// If we're not on an extended access station, the ID is already configured correctly from MapInit.
|
|
||||||
if (station == null || !Comp<StationJobsComponent>(station.Value).ExtendedAccess)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SetupIdAccess(uid, card, true);
|
|
||||||
SetupIdName(uid, card);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, PresetIdCardComponent id, MapInitEvent args)
|
|
||||||
{
|
|
||||||
// If a preset ID card is spawned on a station at setup time,
|
|
||||||
// the station may not exist,
|
|
||||||
// or may not yet know whether it is on extended access (players not spawned yet).
|
|
||||||
// PlayerJobsAssigned makes sure extended access is configured correctly in that case.
|
|
||||||
|
|
||||||
var station = _stationSystem.GetOwningStation(uid);
|
var station = _stationSystem.GetOwningStation(uid);
|
||||||
var extended = false;
|
|
||||||
if (station != null)
|
|
||||||
extended = Comp<StationJobsComponent>(station.Value).ExtendedAccess;
|
|
||||||
|
|
||||||
SetupIdAccess(uid, id, extended);
|
// If we're not on an extended access station, the ID is already configured correctly from MapInit.
|
||||||
SetupIdName(uid, id);
|
if (station == null || !Comp<StationJobsComponent>(station.Value).ExtendedAccess)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetupIdAccess(uid, card, true);
|
||||||
|
SetupIdName(uid, card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(EntityUid uid, PresetIdCardComponent id, MapInitEvent args)
|
||||||
|
{
|
||||||
|
// If a preset ID card is spawned on a station at setup time,
|
||||||
|
// the station may not exist,
|
||||||
|
// or may not yet know whether it is on extended access (players not spawned yet).
|
||||||
|
// PlayerJobsAssigned makes sure extended access is configured correctly in that case.
|
||||||
|
|
||||||
|
var station = _stationSystem.GetOwningStation(uid);
|
||||||
|
var extended = false;
|
||||||
|
if (station != null)
|
||||||
|
extended = Comp<StationJobsComponent>(station.Value).ExtendedAccess;
|
||||||
|
|
||||||
|
SetupIdAccess(uid, id, extended);
|
||||||
|
SetupIdName(uid, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupIdName(EntityUid uid, PresetIdCardComponent id)
|
||||||
|
{
|
||||||
|
if (id.IdName == null)
|
||||||
|
return;
|
||||||
|
_cardSystem.TryChangeFullName(uid, id.IdName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupIdAccess(EntityUid uid, PresetIdCardComponent id, bool extended)
|
||||||
|
{
|
||||||
|
if (id.JobName == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_prototypeManager.TryIndex(id.JobName, out JobPrototype? job))
|
||||||
|
{
|
||||||
|
Log.Error($"Invalid job id ({id.JobName}) for preset card");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupIdName(EntityUid uid, PresetIdCardComponent id)
|
_accessSystem.SetAccessToJob(uid, job, extended);
|
||||||
|
|
||||||
|
_cardSystem.TryChangeJobTitle(uid, job.LocalizedName);
|
||||||
|
_cardSystem.TryChangeJobDepartment(uid, job);
|
||||||
|
|
||||||
|
if (_prototypeManager.TryIndex<StatusIconPrototype>(job.Icon, out var jobIcon))
|
||||||
{
|
{
|
||||||
if (id.IdName == null)
|
_cardSystem.TryChangeJobIcon(uid, jobIcon);
|
||||||
return;
|
|
||||||
_cardSystem.TryChangeFullName(uid, id.IdName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetupIdAccess(EntityUid uid, PresetIdCardComponent id, bool extended)
|
|
||||||
{
|
|
||||||
if (id.JobName == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_prototypeManager.TryIndex(id.JobName, out JobPrototype? job))
|
|
||||||
{
|
|
||||||
Log.Error($"Invalid job id ({id.JobName}) for preset card");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_accessSystem.SetAccessToJob(uid, job, extended);
|
|
||||||
|
|
||||||
_cardSystem.TryChangeJobTitle(uid, job.LocalizedName);
|
|
||||||
|
|
||||||
if (_prototypeManager.TryIndex<StatusIconPrototype>(job.Icon, out var jobIcon))
|
|
||||||
{
|
|
||||||
_cardSystem.TryChangeJobIcon(uid, jobIcon);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,19 @@
|
|||||||
using Content.Shared.Medical.SuitSensor;
|
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
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
/// <summary>
|
||||||
[Access(typeof(CrewMonitoringConsoleSystem))]
|
/// List of all currently connected sensors to this console.
|
||||||
public sealed partial class CrewMonitoringConsoleComponent : Component
|
/// </summary>
|
||||||
{
|
public Dictionary<string, SuitSensorStatus> ConnectedSensors = new();
|
||||||
/// <summary>
|
|
||||||
/// List of all currently connected sensors to this console.
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, SuitSensorStatus> ConnectedSensors = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// After what time sensor consider to be lost.
|
/// After what time sensor consider to be lost.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("sensorTimeout"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("sensorTimeout"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float SensorTimeout = 10f;
|
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,62 +4,71 @@ using Content.Server.DeviceNetwork.Systems;
|
|||||||
using Content.Server.PowerCell;
|
using Content.Server.PowerCell;
|
||||||
using Content.Shared.Medical.CrewMonitoring;
|
using Content.Shared.Medical.CrewMonitoring;
|
||||||
using Content.Shared.Medical.SuitSensor;
|
using Content.Shared.Medical.SuitSensor;
|
||||||
|
using Content.Shared.Pinpointer;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.Medical.CrewMonitoring
|
namespace Content.Server.Medical.CrewMonitoring;
|
||||||
|
|
||||||
|
public sealed class CrewMonitoringConsoleSystem : EntitySystem
|
||||||
{
|
{
|
||||||
public sealed class CrewMonitoringConsoleSystem : EntitySystem
|
[Dependency] private readonly PowerCellSystem _cell = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
[Dependency] private readonly PowerCellSystem _cell = default!;
|
base.Initialize();
|
||||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
SubscribeLocalEvent<CrewMonitoringConsoleComponent, ComponentRemove>(OnRemove);
|
||||||
|
SubscribeLocalEvent<CrewMonitoringConsoleComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||||
|
SubscribeLocalEvent<CrewMonitoringConsoleComponent, BoundUIOpenedEvent>(OnUIOpened);
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
private void OnRemove(EntityUid uid, CrewMonitoringConsoleComponent component, ComponentRemove args)
|
||||||
{
|
{
|
||||||
base.Initialize();
|
component.ConnectedSensors.Clear();
|
||||||
SubscribeLocalEvent<CrewMonitoringConsoleComponent, ComponentRemove>(OnRemove);
|
}
|
||||||
SubscribeLocalEvent<CrewMonitoringConsoleComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
|
||||||
SubscribeLocalEvent<CrewMonitoringConsoleComponent, BoundUIOpenedEvent>(OnUIOpened);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRemove(EntityUid uid, CrewMonitoringConsoleComponent component, ComponentRemove args)
|
private void OnPacketReceived(EntityUid uid, CrewMonitoringConsoleComponent component, DeviceNetworkPacketEvent args)
|
||||||
{
|
{
|
||||||
component.ConnectedSensors.Clear();
|
var payload = args.Data;
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPacketReceived(EntityUid uid, CrewMonitoringConsoleComponent component, DeviceNetworkPacketEvent args)
|
// Check command
|
||||||
{
|
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||||
var payload = args.Data;
|
return;
|
||||||
// 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;
|
|
||||||
|
|
||||||
component.ConnectedSensors = sensorStatus;
|
if (command != DeviceNetworkConstants.CmdUpdatedState)
|
||||||
UpdateUserInterface(uid, component);
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUIOpened(EntityUid uid, CrewMonitoringConsoleComponent component, BoundUIOpenedEvent args)
|
if (!payload.TryGetValue(SuitSensorConstants.NET_STATUS_COLLECTION, out Dictionary<string, SuitSensorStatus>? sensorStatus))
|
||||||
{
|
return;
|
||||||
if (!_cell.TryUseActivatableCharge(uid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
UpdateUserInterface(uid, component);
|
component.ConnectedSensors = sensorStatus;
|
||||||
}
|
UpdateUserInterface(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateUserInterface(EntityUid uid, CrewMonitoringConsoleComponent? component = null)
|
private void OnUIOpened(EntityUid uid, CrewMonitoringConsoleComponent component, BoundUIOpenedEvent args)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!_cell.TryUseActivatableCharge(uid))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_uiSystem.TryGetUi(uid, CrewMonitoringUIKey.Key, out var bui))
|
UpdateUserInterface(uid, component);
|
||||||
return;
|
}
|
||||||
|
|
||||||
// update all sensors info
|
private void UpdateUserInterface(EntityUid uid, CrewMonitoringConsoleComponent? component = null)
|
||||||
var allSensors = component.ConnectedSensors.Values.ToList();
|
{
|
||||||
_uiSystem.SetUiState(bui, new CrewMonitoringState(allSensors, component.Snap, component.Precision));
|
if (!Resolve(uid, ref component))
|
||||||
}
|
return;
|
||||||
|
|
||||||
|
if (!_uiSystem.TryGetUi(uid, CrewMonitoringUIKey.Key, out var bui))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +1,75 @@
|
|||||||
using Content.Shared.Medical.SuitSensor;
|
using Content.Shared.Medical.SuitSensor;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
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.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[Access(typeof(SuitSensorSystem))]
|
||||||
|
public sealed partial class SuitSensorComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracking device, embedded in almost all uniforms and jumpsuits.
|
/// Choose a random sensor mode when item is spawned.
|
||||||
/// If enabled, will report to crew monitoring console owners position and status.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[DataField("randomMode")]
|
||||||
[Access(typeof(SuitSensorSystem))]
|
public bool RandomMode = true;
|
||||||
public sealed partial class SuitSensorComponent : Component
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Choose a random sensor mode when item is spawned.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("randomMode")]
|
|
||||||
public bool RandomMode = true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If true user can't change suit sensor mode
|
/// If true user can't change suit sensor mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("controlsLocked")]
|
[DataField("controlsLocked")]
|
||||||
public bool ControlsLocked = false;
|
public bool ControlsLocked = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current sensor mode. Can be switched by user verbs.
|
/// Current sensor mode. Can be switched by user verbs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("mode")]
|
[DataField("mode")]
|
||||||
public SuitSensorMode Mode = SuitSensorMode.SensorOff;
|
public SuitSensorMode Mode = SuitSensorMode.SensorOff;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Activate sensor if user wear it in this slot.
|
/// Activate sensor if user wear it in this slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("activationSlot")]
|
[DataField("activationSlot")]
|
||||||
public string ActivationSlot = "jumpsuit";
|
public string ActivationSlot = "jumpsuit";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Activate sensor if user has this in a sensor-compatible container.
|
/// Activate sensor if user has this in a sensor-compatible container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("activationContainer")]
|
[DataField("activationContainer")]
|
||||||
public string? ActivationContainer;
|
public string? ActivationContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How often does sensor update its owners status (in seconds). Limited by the system update rate.
|
/// How often does sensor update its owners status (in seconds). Limited by the system update rate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("updateRate")]
|
[DataField("updateRate")]
|
||||||
public TimeSpan UpdateRate = TimeSpan.FromSeconds(2f);
|
public TimeSpan UpdateRate = TimeSpan.FromSeconds(2f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current user that wears suit sensor. Null if nobody wearing it.
|
/// Current user that wears suit sensor. Null if nobody wearing it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public EntityUid? User = null;
|
public EntityUid? User = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Next time when sensor updated owners status
|
/// Next time when sensor updated owners status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("nextUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
[DataField("nextUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||||
public TimeSpan NextUpdate = TimeSpan.Zero;
|
public TimeSpan NextUpdate = TimeSpan.Zero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work.
|
/// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("station")]
|
[DataField("station")]
|
||||||
public EntityUid? StationId = null;
|
public EntityUid? StationId = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The server the suit sensor sends it state to.
|
/// The server the suit sensor sends it state to.
|
||||||
/// The suit sensor will try connecting to a new server when no server is connected.
|
/// The suit sensor will try connecting to a new server when no server is connected.
|
||||||
/// It does this by calling the servers entity system for performance reasons.
|
/// It does this by calling the servers entity system for performance reasons.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("server")]
|
[DataField("server")]
|
||||||
public string? ConnectedServer = null;
|
public string? ConnectedServer = null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,399 +18,409 @@ using Robust.Shared.Map;
|
|||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Medical.SuitSensors
|
namespace Content.Server.Medical.SuitSensors;
|
||||||
|
|
||||||
|
public sealed class SuitSensorSystem : EntitySystem
|
||||||
{
|
{
|
||||||
public sealed class SuitSensorSystem : EntitySystem
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly CrewMonitoringServerSystem _monitoringServerSystem = default!;
|
||||||
|
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||||
|
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
||||||
|
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||||
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
base.Initialize();
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
|
||||||
[Dependency] private readonly CrewMonitoringServerSystem _monitoringServerSystem = default!;
|
SubscribeLocalEvent<SuitSensorComponent, MapInitEvent>(OnMapInit);
|
||||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
SubscribeLocalEvent<SuitSensorComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
SubscribeLocalEvent<SuitSensorComponent, GotEquippedEvent>(OnEquipped);
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
SubscribeLocalEvent<SuitSensorComponent, GotUnequippedEvent>(OnUnequipped);
|
||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
SubscribeLocalEvent<SuitSensorComponent, GetVerbsEvent<Verb>>(OnVerb);
|
||||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
SubscribeLocalEvent<SuitSensorComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
|
||||||
|
SubscribeLocalEvent<SuitSensorComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
private void OnUnpaused(EntityUid uid, SuitSensorComponent component, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
component.NextUpdate += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
var curTime = _gameTiming.CurTime;
|
||||||
|
var sensors = EntityManager.EntityQueryEnumerator<SuitSensorComponent, DeviceNetworkComponent>();
|
||||||
|
|
||||||
|
while (sensors.MoveNext(out var uid, out var sensor, out var device))
|
||||||
{
|
{
|
||||||
base.Initialize();
|
if (device.TransmitFrequency is null)
|
||||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
|
continue;
|
||||||
SubscribeLocalEvent<SuitSensorComponent, MapInitEvent>(OnMapInit);
|
|
||||||
SubscribeLocalEvent<SuitSensorComponent, EntityUnpausedEvent>(OnUnpaused);
|
|
||||||
SubscribeLocalEvent<SuitSensorComponent, GotEquippedEvent>(OnEquipped);
|
|
||||||
SubscribeLocalEvent<SuitSensorComponent, GotUnequippedEvent>(OnUnequipped);
|
|
||||||
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
|
|
||||||
SubscribeLocalEvent<SuitSensorComponent, GetVerbsEvent<Verb>>(OnVerb);
|
|
||||||
SubscribeLocalEvent<SuitSensorComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
|
|
||||||
SubscribeLocalEvent<SuitSensorComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUnpaused(EntityUid uid, SuitSensorComponent component, ref EntityUnpausedEvent args)
|
// check if sensor is ready to update
|
||||||
{
|
if (curTime < sensor.NextUpdate)
|
||||||
component.NextUpdate += args.PausedTime;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
if (!CheckSensorAssignedStation(uid, sensor))
|
||||||
{
|
continue;
|
||||||
base.Update(frameTime);
|
|
||||||
|
|
||||||
var curTime = _gameTiming.CurTime;
|
// TODO: This would cause imprecision at different tick rates.
|
||||||
var sensors = EntityManager.EntityQueryEnumerator<SuitSensorComponent, DeviceNetworkComponent>();
|
sensor.NextUpdate = curTime + sensor.UpdateRate;
|
||||||
|
|
||||||
while (sensors.MoveNext(out var uid, out var sensor, out var device))
|
// get sensor status
|
||||||
|
var status = GetSensorState(uid, sensor);
|
||||||
|
if (status == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//Retrieve active server address if the sensor isn't connected to a server
|
||||||
|
if (sensor.ConnectedServer == null)
|
||||||
{
|
{
|
||||||
if (device.TransmitFrequency is null)
|
if (!_monitoringServerSystem.TryGetActiveServerAddress(sensor.StationId!.Value, out var address))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// check if sensor is ready to update
|
sensor.ConnectedServer = address;
|
||||||
if (curTime < sensor.NextUpdate)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!CheckSensorAssignedStation(uid, sensor))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO: This would cause imprecision at different tick rates.
|
|
||||||
sensor.NextUpdate = curTime + sensor.UpdateRate;
|
|
||||||
|
|
||||||
// get sensor status
|
|
||||||
var status = GetSensorState(uid, sensor);
|
|
||||||
if (status == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
//Retrieve active server address if the sensor isn't connected to a server
|
|
||||||
if (sensor.ConnectedServer == null)
|
|
||||||
{
|
|
||||||
if (!_monitoringServerSystem.TryGetActiveServerAddress(sensor.StationId!.Value, out var address))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
sensor.ConnectedServer = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send it to the connected server
|
|
||||||
var payload = SuitSensorToPacket(status);
|
|
||||||
|
|
||||||
// Clear the connected server if its address isn't on the network
|
|
||||||
if (!_deviceNetworkSystem.IsAddressPresent(device.DeviceNetId, sensor.ConnectedServer))
|
|
||||||
{
|
|
||||||
sensor.ConnectedServer = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_deviceNetworkSystem.QueuePacket(uid, sensor.ConnectedServer, payload, device: device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether the sensor is assigned to a station or not
|
|
||||||
/// and tries to assign an unassigned sensor to a station if it's currently on a grid
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the sensor is assigned to a station or assigning it was successful. False otherwise.</returns>
|
|
||||||
private bool CheckSensorAssignedStation(EntityUid uid, SuitSensorComponent sensor)
|
|
||||||
{
|
|
||||||
if (!sensor.StationId.HasValue && Transform(uid).GridUid == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
sensor.StationId = _stationSystem.GetOwningStation(uid);
|
|
||||||
return sensor.StationId.HasValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev)
|
|
||||||
{
|
|
||||||
// If the player spawns in arrivals then the grid underneath them may not be appropriate.
|
|
||||||
// in which case we'll just use the station spawn code told us they are attached to and set all of their
|
|
||||||
// sensors.
|
|
||||||
var sensorQuery = GetEntityQuery<SuitSensorComponent>();
|
|
||||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
|
||||||
RecursiveSensor(ev.Mob, ev.Station, sensorQuery, xformQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RecursiveSensor(EntityUid uid, EntityUid stationUid, EntityQuery<SuitSensorComponent> sensorQuery, EntityQuery<TransformComponent> xformQuery)
|
|
||||||
{
|
|
||||||
var xform = xformQuery.GetComponent(uid);
|
|
||||||
var enumerator = xform.ChildEnumerator;
|
|
||||||
|
|
||||||
while (enumerator.MoveNext(out var child))
|
|
||||||
{
|
|
||||||
if (sensorQuery.TryGetComponent(child, out var sensor))
|
|
||||||
{
|
|
||||||
sensor.StationId = stationUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
RecursiveSensor(child.Value, stationUid, sensorQuery, xformQuery);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args)
|
|
||||||
{
|
|
||||||
// Fallback
|
|
||||||
component.StationId ??= _stationSystem.GetOwningStation(uid);
|
|
||||||
|
|
||||||
// generate random mode
|
|
||||||
if (component.RandomMode)
|
|
||||||
{
|
|
||||||
//make the sensor mode favor higher levels, except coords.
|
|
||||||
var modesDist = new[]
|
|
||||||
{
|
|
||||||
SuitSensorMode.SensorOff,
|
|
||||||
SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary,
|
|
||||||
SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals,
|
|
||||||
SuitSensorMode.SensorCords, SuitSensorMode.SensorCords
|
|
||||||
};
|
|
||||||
component.Mode = _random.Pick(modesDist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEquipped(EntityUid uid, SuitSensorComponent component, GotEquippedEvent args)
|
|
||||||
{
|
|
||||||
if (args.Slot != component.ActivationSlot)
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.User = args.Equipee;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUnequipped(EntityUid uid, SuitSensorComponent component, GotUnequippedEvent args)
|
|
||||||
{
|
|
||||||
if (args.Slot != component.ActivationSlot)
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.User = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExamine(EntityUid uid, SuitSensorComponent component, ExaminedEvent args)
|
|
||||||
{
|
|
||||||
if (!args.IsInDetailsRange)
|
|
||||||
return;
|
|
||||||
|
|
||||||
string msg;
|
|
||||||
switch (component.Mode)
|
|
||||||
{
|
|
||||||
case SuitSensorMode.SensorOff:
|
|
||||||
msg = "suit-sensor-examine-off";
|
|
||||||
break;
|
|
||||||
case SuitSensorMode.SensorBinary:
|
|
||||||
msg = "suit-sensor-examine-binary";
|
|
||||||
break;
|
|
||||||
case SuitSensorMode.SensorVitals:
|
|
||||||
msg = "suit-sensor-examine-vitals";
|
|
||||||
break;
|
|
||||||
case SuitSensorMode.SensorCords:
|
|
||||||
msg = "suit-sensor-examine-cords";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args.PushMarkup(Loc.GetString(msg));
|
// Send it to the connected server
|
||||||
}
|
var payload = SuitSensorToPacket(status);
|
||||||
|
|
||||||
private void OnVerb(EntityUid uid, SuitSensorComponent component, GetVerbsEvent<Verb> args)
|
// Clear the connected server if its address isn't on the network
|
||||||
{
|
if (!_deviceNetworkSystem.IsAddressPresent(device.DeviceNetId, sensor.ConnectedServer))
|
||||||
// check if user can change sensor
|
|
||||||
if (component.ControlsLocked)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// standard interaction checks
|
|
||||||
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
args.Verbs.UnionWith(new[]
|
|
||||||
{
|
{
|
||||||
CreateVerb(uid, component, args.User, SuitSensorMode.SensorOff),
|
sensor.ConnectedServer = null;
|
||||||
CreateVerb(uid, component, args.User, SuitSensorMode.SensorBinary),
|
continue;
|
||||||
CreateVerb(uid, component, args.User, SuitSensorMode.SensorVitals),
|
|
||||||
CreateVerb(uid, component, args.User, SuitSensorMode.SensorCords)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInsert(EntityUid uid, SuitSensorComponent component, EntGotInsertedIntoContainerMessage args)
|
|
||||||
{
|
|
||||||
if (args.Container.ID != component.ActivationContainer)
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.User = args.Container.Owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRemove(EntityUid uid, SuitSensorComponent component, EntGotRemovedFromContainerMessage args)
|
|
||||||
{
|
|
||||||
if (args.Container.ID != component.ActivationContainer)
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.User = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Verb CreateVerb(EntityUid uid, SuitSensorComponent component, EntityUid userUid, SuitSensorMode mode)
|
|
||||||
{
|
|
||||||
return new Verb()
|
|
||||||
{
|
|
||||||
Text = GetModeName(mode),
|
|
||||||
Disabled = component.Mode == mode,
|
|
||||||
Priority = -(int) mode, // sort them in descending order
|
|
||||||
Category = VerbCategory.SetSensor,
|
|
||||||
Act = () => SetSensor(uid, mode, userUid, component)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetModeName(SuitSensorMode mode)
|
|
||||||
{
|
|
||||||
string name;
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case SuitSensorMode.SensorOff:
|
|
||||||
name = "suit-sensor-mode-off";
|
|
||||||
break;
|
|
||||||
case SuitSensorMode.SensorBinary:
|
|
||||||
name = "suit-sensor-mode-binary";
|
|
||||||
break;
|
|
||||||
case SuitSensorMode.SensorVitals:
|
|
||||||
name = "suit-sensor-mode-vitals";
|
|
||||||
break;
|
|
||||||
case SuitSensorMode.SensorCords:
|
|
||||||
name = "suit-sensor-mode-cords";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Loc.GetString(name);
|
_deviceNetworkSystem.QueuePacket(uid, sensor.ConnectedServer, payload, device: device);
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSensor(EntityUid uid, SuitSensorMode mode, EntityUid? userUid = null,
|
|
||||||
SuitSensorComponent? component = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref component))
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.Mode = mode;
|
|
||||||
|
|
||||||
if (userUid != null)
|
|
||||||
{
|
|
||||||
var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode)));
|
|
||||||
_popupSystem.PopupEntity(msg, uid, userUid.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SuitSensorStatus? GetSensorState(EntityUid uid, SuitSensorComponent? sensor = null, TransformComponent? transform = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref sensor, ref transform))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// check if sensor is enabled and worn by user
|
|
||||||
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || transform.GridUid == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get health mob state
|
|
||||||
var isAlive = false;
|
|
||||||
if (EntityManager.TryGetComponent(sensor.User.Value, out MobStateComponent? mobState))
|
|
||||||
isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState);
|
|
||||||
|
|
||||||
// get mob total damage
|
|
||||||
var totalDamage = 0;
|
|
||||||
if (TryComp<DamageableComponent>(sensor.User.Value, out var damageable))
|
|
||||||
totalDamage = damageable.TotalDamage.Int();
|
|
||||||
|
|
||||||
// finally, form suit sensor status
|
|
||||||
var status = new SuitSensorStatus(GetNetEntity(uid), userName, userJob);
|
|
||||||
switch (sensor.Mode)
|
|
||||||
{
|
|
||||||
case SuitSensorMode.SensorBinary:
|
|
||||||
status.IsAlive = isAlive;
|
|
||||||
break;
|
|
||||||
case SuitSensorMode.SensorVitals:
|
|
||||||
status.IsAlive = isAlive;
|
|
||||||
status.TotalDamage = totalDamage;
|
|
||||||
break;
|
|
||||||
case SuitSensorMode.SensorCords:
|
|
||||||
status.IsAlive = isAlive;
|
|
||||||
status.TotalDamage = totalDamage;
|
|
||||||
EntityCoordinates coordinates;
|
|
||||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
|
||||||
|
|
||||||
if (transform.GridUid != null)
|
|
||||||
{
|
|
||||||
coordinates = new EntityCoordinates(transform.GridUid.Value,
|
|
||||||
_transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery)
|
|
||||||
.Transform(_transform.GetWorldPosition(transform, xformQuery)));
|
|
||||||
}
|
|
||||||
else if (transform.MapUid != null)
|
|
||||||
{
|
|
||||||
coordinates = new EntityCoordinates(transform.MapUid.Value,
|
|
||||||
_transform.GetWorldPosition(transform, xformQuery));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
coordinates = EntityCoordinates.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
status.Coordinates = GetNetCoordinates(coordinates);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize create a device network package from the suit sensors status.
|
|
||||||
/// </summary>
|
|
||||||
public NetworkPayload SuitSensorToPacket(SuitSensorStatus status)
|
|
||||||
{
|
|
||||||
var payload = new NetworkPayload()
|
|
||||||
{
|
|
||||||
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
|
|
||||||
[SuitSensorConstants.NET_NAME] = status.Name,
|
|
||||||
[SuitSensorConstants.NET_JOB] = status.Job,
|
|
||||||
[SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive,
|
|
||||||
[SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (status.TotalDamage != null)
|
|
||||||
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
|
|
||||||
if (status.Coordinates != null)
|
|
||||||
payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates);
|
|
||||||
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Try to create the suit sensors status from the device network message
|
|
||||||
/// </summary>
|
|
||||||
public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload)
|
|
||||||
{
|
|
||||||
// check command
|
|
||||||
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
|
||||||
return null;
|
|
||||||
if (command != DeviceNetworkConstants.CmdUpdatedState)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// 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_IS_ALIVE, out bool? isAlive)) return null;
|
|
||||||
if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null;
|
|
||||||
|
|
||||||
// try get total damage and cords (optionals)
|
|
||||||
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
|
|
||||||
payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords);
|
|
||||||
|
|
||||||
var status = new SuitSensorStatus(suitSensorUid, name, job)
|
|
||||||
{
|
|
||||||
IsAlive = isAlive.Value,
|
|
||||||
TotalDamage = totalDamage,
|
|
||||||
Coordinates = coords,
|
|
||||||
};
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the sensor is assigned to a station or not
|
||||||
|
/// and tries to assign an unassigned sensor to a station if it's currently on a grid
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the sensor is assigned to a station or assigning it was successful. False otherwise.</returns>
|
||||||
|
private bool CheckSensorAssignedStation(EntityUid uid, SuitSensorComponent sensor)
|
||||||
|
{
|
||||||
|
if (!sensor.StationId.HasValue && Transform(uid).GridUid == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
sensor.StationId = _stationSystem.GetOwningStation(uid);
|
||||||
|
return sensor.StationId.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev)
|
||||||
|
{
|
||||||
|
// If the player spawns in arrivals then the grid underneath them may not be appropriate.
|
||||||
|
// in which case we'll just use the station spawn code told us they are attached to and set all of their
|
||||||
|
// sensors.
|
||||||
|
var sensorQuery = GetEntityQuery<SuitSensorComponent>();
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
RecursiveSensor(ev.Mob, ev.Station, sensorQuery, xformQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecursiveSensor(EntityUid uid, EntityUid stationUid, EntityQuery<SuitSensorComponent> sensorQuery, EntityQuery<TransformComponent> xformQuery)
|
||||||
|
{
|
||||||
|
var xform = xformQuery.GetComponent(uid);
|
||||||
|
var enumerator = xform.ChildEnumerator;
|
||||||
|
|
||||||
|
while (enumerator.MoveNext(out var child))
|
||||||
|
{
|
||||||
|
if (sensorQuery.TryGetComponent(child, out var sensor))
|
||||||
|
{
|
||||||
|
sensor.StationId = stationUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
RecursiveSensor(child.Value, stationUid, sensorQuery, xformQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args)
|
||||||
|
{
|
||||||
|
// Fallback
|
||||||
|
component.StationId ??= _stationSystem.GetOwningStation(uid);
|
||||||
|
|
||||||
|
// generate random mode
|
||||||
|
if (component.RandomMode)
|
||||||
|
{
|
||||||
|
//make the sensor mode favor higher levels, except coords.
|
||||||
|
var modesDist = new[]
|
||||||
|
{
|
||||||
|
SuitSensorMode.SensorOff,
|
||||||
|
SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary,
|
||||||
|
SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals,
|
||||||
|
SuitSensorMode.SensorCords, SuitSensorMode.SensorCords
|
||||||
|
};
|
||||||
|
component.Mode = _random.Pick(modesDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEquipped(EntityUid uid, SuitSensorComponent component, GotEquippedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Slot != component.ActivationSlot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.User = args.Equipee;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnequipped(EntityUid uid, SuitSensorComponent component, GotUnequippedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Slot != component.ActivationSlot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.User = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamine(EntityUid uid, SuitSensorComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (!args.IsInDetailsRange)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string msg;
|
||||||
|
switch (component.Mode)
|
||||||
|
{
|
||||||
|
case SuitSensorMode.SensorOff:
|
||||||
|
msg = "suit-sensor-examine-off";
|
||||||
|
break;
|
||||||
|
case SuitSensorMode.SensorBinary:
|
||||||
|
msg = "suit-sensor-examine-binary";
|
||||||
|
break;
|
||||||
|
case SuitSensorMode.SensorVitals:
|
||||||
|
msg = "suit-sensor-examine-vitals";
|
||||||
|
break;
|
||||||
|
case SuitSensorMode.SensorCords:
|
||||||
|
msg = "suit-sensor-examine-cords";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.PushMarkup(Loc.GetString(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVerb(EntityUid uid, SuitSensorComponent component, GetVerbsEvent<Verb> args)
|
||||||
|
{
|
||||||
|
// check if user can change sensor
|
||||||
|
if (component.ControlsLocked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// standard interaction checks
|
||||||
|
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Verbs.UnionWith(new[]
|
||||||
|
{
|
||||||
|
CreateVerb(uid, component, args.User, SuitSensorMode.SensorOff),
|
||||||
|
CreateVerb(uid, component, args.User, SuitSensorMode.SensorBinary),
|
||||||
|
CreateVerb(uid, component, args.User, SuitSensorMode.SensorVitals),
|
||||||
|
CreateVerb(uid, component, args.User, SuitSensorMode.SensorCords)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInsert(EntityUid uid, SuitSensorComponent component, EntGotInsertedIntoContainerMessage args)
|
||||||
|
{
|
||||||
|
if (args.Container.ID != component.ActivationContainer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.User = args.Container.Owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRemove(EntityUid uid, SuitSensorComponent component, EntGotRemovedFromContainerMessage args)
|
||||||
|
{
|
||||||
|
if (args.Container.ID != component.ActivationContainer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.User = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Verb CreateVerb(EntityUid uid, SuitSensorComponent component, EntityUid userUid, SuitSensorMode mode)
|
||||||
|
{
|
||||||
|
return new Verb()
|
||||||
|
{
|
||||||
|
Text = GetModeName(mode),
|
||||||
|
Disabled = component.Mode == mode,
|
||||||
|
Priority = -(int) mode, // sort them in descending order
|
||||||
|
Category = VerbCategory.SetSensor,
|
||||||
|
Act = () => SetSensor(uid, mode, userUid, component)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetModeName(SuitSensorMode mode)
|
||||||
|
{
|
||||||
|
string name;
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case SuitSensorMode.SensorOff:
|
||||||
|
name = "suit-sensor-mode-off";
|
||||||
|
break;
|
||||||
|
case SuitSensorMode.SensorBinary:
|
||||||
|
name = "suit-sensor-mode-binary";
|
||||||
|
break;
|
||||||
|
case SuitSensorMode.SensorVitals:
|
||||||
|
name = "suit-sensor-mode-vitals";
|
||||||
|
break;
|
||||||
|
case SuitSensorMode.SensorCords:
|
||||||
|
name = "suit-sensor-mode-cords";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Loc.GetString(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSensor(EntityUid uid, SuitSensorMode mode, EntityUid? userUid = null,
|
||||||
|
SuitSensorComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Mode = mode;
|
||||||
|
|
||||||
|
if (userUid != null)
|
||||||
|
{
|
||||||
|
var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode)));
|
||||||
|
_popupSystem.PopupEntity(msg, uid, userUid.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SuitSensorStatus? GetSensorState(EntityUid uid, SuitSensorComponent? sensor = null, TransformComponent? transform = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref sensor, ref transform))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// check if sensor is enabled and worn by user
|
||||||
|
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || transform.GridUid == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
var isAlive = false;
|
||||||
|
if (EntityManager.TryGetComponent(sensor.User.Value, out MobStateComponent? mobState))
|
||||||
|
isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState);
|
||||||
|
|
||||||
|
// get mob total damage
|
||||||
|
var totalDamage = 0;
|
||||||
|
if (TryComp<DamageableComponent>(sensor.User.Value, out var damageable))
|
||||||
|
totalDamage = damageable.TotalDamage.Int();
|
||||||
|
|
||||||
|
// finally, form suit sensor status
|
||||||
|
var status = new SuitSensorStatus(GetNetEntity(uid), userName, userJob, userJobIcon, userJobDepartments);
|
||||||
|
switch (sensor.Mode)
|
||||||
|
{
|
||||||
|
case SuitSensorMode.SensorBinary:
|
||||||
|
status.IsAlive = isAlive;
|
||||||
|
break;
|
||||||
|
case SuitSensorMode.SensorVitals:
|
||||||
|
status.IsAlive = isAlive;
|
||||||
|
status.TotalDamage = totalDamage;
|
||||||
|
break;
|
||||||
|
case SuitSensorMode.SensorCords:
|
||||||
|
status.IsAlive = isAlive;
|
||||||
|
status.TotalDamage = totalDamage;
|
||||||
|
EntityCoordinates coordinates;
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
|
if (transform.GridUid != null)
|
||||||
|
{
|
||||||
|
coordinates = new EntityCoordinates(transform.GridUid.Value,
|
||||||
|
_transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery)
|
||||||
|
.Transform(_transform.GetWorldPosition(transform, xformQuery)));
|
||||||
|
}
|
||||||
|
else if (transform.MapUid != null)
|
||||||
|
{
|
||||||
|
coordinates = new EntityCoordinates(transform.MapUid.Value,
|
||||||
|
_transform.GetWorldPosition(transform, xformQuery));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
coordinates = EntityCoordinates.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.Coordinates = GetNetCoordinates(coordinates);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize create a device network package from the suit sensors status.
|
||||||
|
/// </summary>
|
||||||
|
public NetworkPayload SuitSensorToPacket(SuitSensorStatus status)
|
||||||
|
{
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
[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,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.TotalDamage != null)
|
||||||
|
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
|
||||||
|
if (status.Coordinates != null)
|
||||||
|
payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to create the suit sensors status from the device network message
|
||||||
|
/// </summary>
|
||||||
|
public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload)
|
||||||
|
{
|
||||||
|
// check command
|
||||||
|
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||||
|
return null;
|
||||||
|
if (command != DeviceNetworkConstants.CmdUpdatedState)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// try get total damage and cords (optionals)
|
||||||
|
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
|
||||||
|
payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords);
|
||||||
|
|
||||||
|
var status = new SuitSensorStatus(suitSensorUid, name, job, jobIcon, jobDepartments)
|
||||||
|
{
|
||||||
|
IsAlive = isAlive.Value,
|
||||||
|
TotalDamage = totalDamage,
|
||||||
|
Coordinates = coords,
|
||||||
|
};
|
||||||
|
return status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,29 +4,34 @@ using Content.Shared.StatusIcon;
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
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)]
|
||||||
|
public sealed partial class IdCardComponent : Component
|
||||||
{
|
{
|
||||||
[RegisterComponent, NetworkedComponent]
|
[DataField("fullName"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
[AutoGenerateComponentState]
|
[AutoNetworkedField]
|
||||||
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)]
|
// FIXME Friends
|
||||||
public sealed partial class IdCardComponent : Component
|
public string? FullName;
|
||||||
{
|
|
||||||
[DataField("fullName"), ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[AutoNetworkedField]
|
|
||||||
// FIXME Friends
|
|
||||||
public string? FullName;
|
|
||||||
|
|
||||||
[DataField("jobTitle")]
|
[DataField("jobTitle")]
|
||||||
[AutoNetworkedField]
|
[AutoNetworkedField]
|
||||||
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite), ViewVariables(VVAccess.ReadWrite)]
|
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public string? JobTitle;
|
public string? JobTitle;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The state of the job icon rsi.
|
/// The state of the job icon rsi.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("jobIcon", customTypeSerializer: typeof(PrototypeIdSerializer<StatusIconPrototype>))]
|
[DataField("jobIcon", customTypeSerializer: typeof(PrototypeIdSerializer<StatusIconPrototype>))]
|
||||||
[AutoNetworkedField]
|
[AutoNetworkedField]
|
||||||
public string JobIcon = "JobIconUnknown";
|
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,27 +1,21 @@
|
|||||||
using Content.Shared.Medical.SuitSensor;
|
using Content.Shared.Medical.SuitSensor;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Medical.CrewMonitoring
|
namespace Content.Shared.Medical.CrewMonitoring;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum CrewMonitoringUIKey
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
Key
|
||||||
public enum CrewMonitoringUIKey
|
}
|
||||||
{
|
|
||||||
Key
|
[Serializable, NetSerializable]
|
||||||
}
|
public sealed class CrewMonitoringState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
[Serializable, NetSerializable]
|
public List<SuitSensorStatus> Sensors;
|
||||||
public sealed class CrewMonitoringState : BoundUserInterfaceState
|
|
||||||
{
|
public CrewMonitoringState(List<SuitSensorStatus> sensors)
|
||||||
public List<SuitSensorStatus> Sensors;
|
{
|
||||||
public readonly bool Snap;
|
Sensors = sensors;
|
||||||
public readonly float Precision;
|
}
|
||||||
|
|
||||||
public CrewMonitoringState(List<SuitSensorStatus> sensors, bool snap, float precision)
|
|
||||||
{
|
|
||||||
Sensors = sensors;
|
|
||||||
Snap = snap;
|
|
||||||
Precision = precision;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,66 @@
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Medical.SuitSensor
|
namespace Content.Shared.Medical.SuitSensor;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SuitSensorStatus
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
public SuitSensorStatus(NetEntity suitSensorUid, string name, string job, string jobIcon, List<string> jobDepartments)
|
||||||
public sealed class SuitSensorStatus
|
|
||||||
{
|
{
|
||||||
public SuitSensorStatus(NetEntity suitSensorUid, string name, string job)
|
SuitSensorUid = suitSensorUid;
|
||||||
{
|
Name = name;
|
||||||
SuitSensorUid = suitSensorUid;
|
Job = job;
|
||||||
Name = name;
|
JobIcon = jobIcon;
|
||||||
Job = job;
|
JobDepartments = jobDepartments;
|
||||||
}
|
|
||||||
|
|
||||||
public TimeSpan Timestamp;
|
|
||||||
public NetEntity SuitSensorUid;
|
|
||||||
public string Name;
|
|
||||||
public string Job;
|
|
||||||
public bool IsAlive;
|
|
||||||
public int? TotalDamage;
|
|
||||||
public NetCoordinates? Coordinates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
public TimeSpan Timestamp;
|
||||||
public enum SuitSensorMode : byte
|
public NetEntity SuitSensorUid;
|
||||||
{
|
public string Name;
|
||||||
/// <summary>
|
public string Job;
|
||||||
/// Sensor doesn't send any information about owner
|
public string JobIcon;
|
||||||
/// </summary>
|
public List<string> JobDepartments;
|
||||||
SensorOff = 0,
|
public bool IsAlive;
|
||||||
|
public int? TotalDamage;
|
||||||
/// <summary>
|
public NetCoordinates? Coordinates;
|
||||||
/// Sensor sends only binary status (alive/dead)
|
}
|
||||||
/// </summary>
|
|
||||||
SensorBinary = 1,
|
[Serializable, NetSerializable]
|
||||||
|
public enum SuitSensorMode : byte
|
||||||
/// <summary>
|
{
|
||||||
/// Sensor sends health vitals status
|
/// <summary>
|
||||||
/// </summary>
|
/// Sensor doesn't send any information about owner
|
||||||
SensorVitals = 2,
|
/// </summary>
|
||||||
|
SensorOff = 0,
|
||||||
/// <summary>
|
|
||||||
/// Sensor sends vitals status and GPS position
|
/// <summary>
|
||||||
/// </summary>
|
/// Sensor sends only binary status (alive/dead)
|
||||||
SensorCords = 3
|
/// </summary>
|
||||||
}
|
SensorBinary = 1,
|
||||||
|
|
||||||
public static class SuitSensorConstants
|
/// <summary>
|
||||||
{
|
/// Sensor sends health vitals status
|
||||||
public const string NET_NAME = "name";
|
/// </summary>
|
||||||
public const string NET_JOB = "job";
|
SensorVitals = 2,
|
||||||
public const string NET_IS_ALIVE = "alive";
|
|
||||||
public const string NET_TOTAL_DAMAGE = "vitals";
|
/// <summary>
|
||||||
public const string NET_COORDINATES = "coords";
|
/// Sensor sends vitals status and GPS position
|
||||||
public const string NET_SUIT_SENSOR_UID = "uid";
|
/// </summary>
|
||||||
|
SensorCords = 3
|
||||||
///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";
|
|
||||||
}
|
public static class SuitSensorConstants
|
||||||
|
{
|
||||||
|
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";
|
||||||
|
public const string NET_SUIT_SENSOR_UID = "uid";
|
||||||
|
|
||||||
|
///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
|
## 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-name = Name
|
||||||
crew-monitoring-user-interface-job = Job
|
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-info = N/A
|
||||||
|
|
||||||
crew-monitoring-user-interface-no-server = Server not found
|
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 |