Station maps (#13027)
@@ -7,15 +7,25 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private CrewMonitoringWindow? _menu;
|
||||
|
||||
public CrewMonitoringBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] Enum uiKey) : base(owner, uiKey)
|
||||
public CrewMonitoringBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
_menu = new CrewMonitoringWindow();
|
||||
EntityUid? gridUid = null;
|
||||
|
||||
if (_entManager.TryGetComponent<TransformComponent>(Owner.Owner, out var xform))
|
||||
{
|
||||
gridUid = xform.GridUid;
|
||||
}
|
||||
|
||||
_menu = new CrewMonitoringWindow(gridUid);
|
||||
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
}
|
||||
@@ -27,7 +37,15 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
switch (state)
|
||||
{
|
||||
case CrewMonitoringState st:
|
||||
_menu?.ShowSensors(st.Sensors, st.WorldPosition, st.Snap, st.Precision);
|
||||
_entManager.TryGetComponent<TransformComponent>(Owner.Owner, out var xform);
|
||||
Vector2 localPosition = Vector2.Zero;
|
||||
|
||||
if (_entManager.TryGetComponent<TransformComponent>(xform?.GridUid, out var gridXform))
|
||||
{
|
||||
localPosition = gridXform.InvWorldMatrix.Transform(xform.WorldPosition);
|
||||
}
|
||||
|
||||
_menu?.ShowSensors(st.Sensors, localPosition, st.Snap, st.Precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'crew-monitoring-user-interface-title'}"
|
||||
SetSize="775 400">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" Margin="5">
|
||||
<GridContainer Name="SensorsTable" HorizontalExpand="True" VerticalExpand="True" HSeparationOverride="5" VSeparationOverride="20" Columns="4">
|
||||
<!-- Category Headers -->
|
||||
<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"/>
|
||||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'crew-monitoring-user-interface-title'}"
|
||||
SetSize="1130 700"
|
||||
MinSize="1130 700">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<ScrollContainer HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="0, 0, 8, 0">
|
||||
<GridContainer Name="SensorsTable"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
HSeparationOverride="5"
|
||||
VSeparationOverride="20"
|
||||
Columns="4">
|
||||
<!-- Table header -->
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-name'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-job'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-status'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-location'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
|
||||
<!-- Additional table rows are filled by code -->
|
||||
</GridContainer>
|
||||
</ScrollContainer>
|
||||
<!-- Table rows are filled by code -->
|
||||
</GridContainer>
|
||||
</ScrollContainer>
|
||||
<ui:NavMapControl Name="NavMap"
|
||||
Margin="5 5"/>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -18,19 +18,32 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
private List<Control> _rowsContent = new();
|
||||
private List<(DirectionIcon Icon, Vector2 Position)> _directionIcons = new();
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IEyeManager _eye;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private EntityUid? _stationUid;
|
||||
|
||||
public static int IconSize = 16; // XAML has a `VSeparationOverride` of 20 for each row.
|
||||
|
||||
public CrewMonitoringWindow()
|
||||
public CrewMonitoringWindow(EntityUid? mapUid)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_eye = IoCManager.Resolve<IEyeManager>();
|
||||
_entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
_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, Vector2 worldPosition, bool snap, float precision)
|
||||
public void ShowSensors(List<SuitSensorStatus> stSensors, Vector2 localPosition, bool snap, float precision)
|
||||
{
|
||||
ClearAllSensors();
|
||||
|
||||
@@ -43,13 +56,21 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
// add users name
|
||||
// format: UserName
|
||||
var nameLabel = new Label()
|
||||
var nameLabel = new PanelContainer()
|
||||
{
|
||||
Text = sensor.Name,
|
||||
HorizontalExpand = true
|
||||
PanelOverride = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = StyleNano.ButtonColorDisabled,
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = sensor.Name,
|
||||
Margin = new Thickness(5f, 5f),
|
||||
}
|
||||
}
|
||||
};
|
||||
SensorsTable.AddChild(nameLabel);
|
||||
_rowsContent.Add(nameLabel);
|
||||
|
||||
// add users job
|
||||
// format: JobName
|
||||
@@ -58,6 +79,9 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
Text = sensor.Job,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
SensorsTable.AddChild(nameLabel);
|
||||
_rowsContent.Add(nameLabel);
|
||||
SensorsTable.AddChild(jobLabel);
|
||||
_rowsContent.Add(jobLabel);
|
||||
|
||||
@@ -79,9 +103,36 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
|
||||
// add users positions
|
||||
// format: (x, y)
|
||||
var box = GetPositionBox(sensor.Coordinates, worldPosition, snap, precision);
|
||||
var box = GetPositionBox(sensor.Coordinates, localPosition, snap, precision);
|
||||
|
||||
SensorsTable.AddChild(box);
|
||||
_rowsContent.Add(box);
|
||||
|
||||
if (sensor.Coordinates != null && NavMap.Visible)
|
||||
{
|
||||
NavMap.TrackedCoordinates.Add(sensor.Coordinates.Value, (true, Color.FromHex("#B02E26")));
|
||||
nameLabel.MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
// Hide all others upon mouseover.
|
||||
nameLabel.OnMouseEntered += args =>
|
||||
{
|
||||
foreach (var (coord, value) in NavMap.TrackedCoordinates)
|
||||
{
|
||||
if (coord == sensor.Coordinates)
|
||||
continue;
|
||||
|
||||
NavMap.TrackedCoordinates[coord] = (false, value.Color);
|
||||
}
|
||||
};
|
||||
|
||||
nameLabel.OnMouseExited += args =>
|
||||
{
|
||||
foreach (var (coord, value) in NavMap.TrackedCoordinates)
|
||||
{
|
||||
NavMap.TrackedCoordinates[coord] = (true, value.Color);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +140,7 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal };
|
||||
|
||||
if (coordinates == null)
|
||||
if (coordinates == null || !_entManager.TryGetComponent<TransformComponent>(_stationUid, out var xform))
|
||||
{
|
||||
var dirIcon = new DirectionIcon()
|
||||
{
|
||||
@@ -101,17 +152,18 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo: add locations names (kitchen, bridge, etc)
|
||||
var pos = (Vector2i) coordinates.Value.Position;
|
||||
var mapCoords = coordinates.Value.ToMap(_entityManager).Position;
|
||||
var position = coordinates.Value.ToMapPos(_entManager);
|
||||
var local = xform.InvWorldMatrix.Transform(position);
|
||||
|
||||
var displayPos = local.Floored();
|
||||
var dirIcon = new DirectionIcon(snap, precision)
|
||||
{
|
||||
SetSize = (IconSize, IconSize),
|
||||
Margin = new(0, 0, 4, 0)
|
||||
};
|
||||
box.AddChild(dirIcon);
|
||||
box.AddChild(new Label() { Text = pos.ToString() });
|
||||
_directionIcons.Add((dirIcon, mapCoords - sensorPosition));
|
||||
box.AddChild(new Label() { Text = displayPos.ToString() });
|
||||
_directionIcons.Add((dirIcon, local - sensorPosition));
|
||||
}
|
||||
|
||||
return box;
|
||||
@@ -134,7 +186,9 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
SensorsTable.RemoveChild(child);
|
||||
}
|
||||
|
||||
_rowsContent.Clear();
|
||||
NavMap.TrackedCoordinates.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
96
Content.Client/Pinpointer/NavMapSystem.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Pinpointer;
|
||||
|
||||
public sealed class NavMapSystem : SharedNavMapSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<NavMapComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not NavMapComponentState state)
|
||||
return;
|
||||
|
||||
component.Chunks.Clear();
|
||||
|
||||
foreach (var (origin, data) in state.TileData)
|
||||
{
|
||||
component.Chunks.Add(origin, new NavMapChunk(origin)
|
||||
{
|
||||
TileData = data,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NavMapOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public NavMapOverlay(IEntityManager entManager, IMapManager mapManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = mapManager;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var query = _entManager.GetEntityQuery<NavMapComponent>();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var scale = Matrix3.CreateScale(new Vector2(1f, 1f));
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
if (!query.TryGetComponent(grid.Owner, out var navMap) || !xformQuery.TryGetComponent(grid.Owner, out var xform))
|
||||
continue;
|
||||
|
||||
// TODO: Faster helper method
|
||||
var (_, _, matrix, invMatrix) = xform.GetWorldPositionRotationMatrixWithInv();
|
||||
|
||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
Matrix3.Multiply(in scale, in matrix, out var matty);
|
||||
|
||||
args.WorldHandle.SetTransform(matty);
|
||||
|
||||
for (var x = Math.Floor(localAABB.Left); x <= Math.Ceiling(localAABB.Right); x += SharedNavMapSystem.ChunkSize * grid.TileSize)
|
||||
{
|
||||
for (var y = Math.Floor(localAABB.Bottom); y <= Math.Ceiling(localAABB.Top); y += SharedNavMapSystem.ChunkSize * grid.TileSize)
|
||||
{
|
||||
var floored = new Vector2i((int) x, (int) y);
|
||||
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize);
|
||||
|
||||
if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
continue;
|
||||
|
||||
// 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;
|
||||
|
||||
var tile = chunk.Origin * SharedNavMapSystem.ChunkSize + SharedNavMapSystem.GetTile(mask);
|
||||
args.WorldHandle.DrawRect(new Box2(tile * grid.TileSize, (tile + 1) * grid.TileSize), Color.Aqua, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Pinpointer
|
||||
{
|
||||
public sealed class ClientPinpointerSystem : SharedPinpointerSystem
|
||||
public sealed class PinpointerSystem : SharedPinpointerSystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
333
Content.Client/Pinpointer/UI/NavMapControl.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Vector2 = Robust.Shared.Maths.Vector2;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Displays the nav map data of the specified grid.
|
||||
/// </summary>
|
||||
public sealed class NavMapControl : MapGridControl
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public EntityUid? MapUid;
|
||||
|
||||
|
||||
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
|
||||
|
||||
private Vector2 _offset;
|
||||
private bool _draggin;
|
||||
|
||||
private bool _recentering = false;
|
||||
|
||||
private float _recenterMinimum = 0.05f;
|
||||
|
||||
// TODO: https://github.com/space-wizards/RobustToolbox/issues/3818
|
||||
private readonly Label _zoom = new()
|
||||
{
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
Margin = new Thickness(8f, 8f),
|
||||
};
|
||||
|
||||
private readonly Button _recenter = new()
|
||||
{
|
||||
Text = "Recentre",
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
Margin = new Thickness(8f, 4f),
|
||||
Disabled = true,
|
||||
};
|
||||
|
||||
public NavMapControl() : base(8f, 128f, 48f)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RectClipContent = true;
|
||||
HorizontalExpand = true;
|
||||
VerticalExpand = true;
|
||||
|
||||
var topPanel = new PanelContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = StyleNano.ButtonColorContext.WithAlpha(1f),
|
||||
BorderColor = StyleNano.PanelDark
|
||||
},
|
||||
VerticalExpand = false,
|
||||
Children =
|
||||
{
|
||||
_zoom,
|
||||
_recenter,
|
||||
}
|
||||
};
|
||||
|
||||
var topContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
topPanel,
|
||||
new Control()
|
||||
{
|
||||
Name = "DrawingControl",
|
||||
VerticalExpand = true,
|
||||
Margin = new Thickness(5f, 5f)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddChild(topContainer);
|
||||
topPanel.Measure(Vector2.Infinity);
|
||||
|
||||
_recenter.OnPressed += args =>
|
||||
{
|
||||
_recentering = true;
|
||||
};
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindDown(args);
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use)
|
||||
{
|
||||
_draggin = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use)
|
||||
{
|
||||
_draggin = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
{
|
||||
base.MouseMove(args);
|
||||
|
||||
if (!_draggin)
|
||||
return;
|
||||
|
||||
_recentering = false;
|
||||
_offset -= new Vector2(args.Relative.X, -args.Relative.Y) / MidPoint * WorldRange;
|
||||
|
||||
if (_offset != Vector2.Zero)
|
||||
{
|
||||
_recenter.Disabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_recenter.Disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (_recentering)
|
||||
{
|
||||
var frameTime = Timing.FrameTime;
|
||||
var diff = _offset * (float) frameTime.TotalSeconds;
|
||||
|
||||
if (_offset.LengthSquared < _recenterMinimum)
|
||||
{
|
||||
_offset = Vector2.Zero;
|
||||
_recentering = false;
|
||||
_recenter.Disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_offset -= diff * 5f;
|
||||
}
|
||||
}
|
||||
|
||||
_zoom.Text = $"Zoom: {(WorldRange / WorldMaxRange * 100f):0.00}%";
|
||||
|
||||
if (!_entManager.TryGetComponent<NavMapComponent>(MapUid, out var navMap) ||
|
||||
!_entManager.TryGetComponent<TransformComponent>(MapUid, out var xform) ||
|
||||
!_entManager.TryGetComponent<MapGridComponent>(MapUid, out var grid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var offset = _offset;
|
||||
var tileColor = new Color(30, 67, 30);
|
||||
var lineColor = new Color(102, 217, 102);
|
||||
|
||||
if (_entManager.TryGetComponent<PhysicsComponent>(MapUid, out var physics))
|
||||
{
|
||||
offset += physics.LocalCenter;
|
||||
}
|
||||
|
||||
// Draw tiles
|
||||
if (_entManager.TryGetComponent<FixturesComponent>(MapUid, out var manager))
|
||||
{
|
||||
Span<Vector2> verts = new Vector2[8];
|
||||
|
||||
foreach (var fixture in manager.Fixtures.Values)
|
||||
{
|
||||
if (fixture.Shape is not PolygonShape poly)
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < poly.VertexCount; i++)
|
||||
{
|
||||
var vert = poly.Vertices[i] - offset;
|
||||
|
||||
verts[i] = Scale(new Vector2(vert.X, -vert.Y));
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], tileColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the wall data
|
||||
var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
|
||||
var tileSize = new Vector2(grid.TileSize, -grid.TileSize);
|
||||
|
||||
for (var x = Math.Floor(area.Left); x <= Math.Ceiling(area.Right); x += SharedNavMapSystem.ChunkSize * grid.TileSize)
|
||||
{
|
||||
for (var y = Math.Floor(area.Bottom); y <= Math.Ceiling(area.Top); y += SharedNavMapSystem.ChunkSize * grid.TileSize)
|
||||
{
|
||||
var floored = new Vector2i((int) x, (int) y);
|
||||
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize);
|
||||
|
||||
if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
continue;
|
||||
|
||||
// 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 - 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var curTime = Timing.RealTime;
|
||||
var blinkFrequency = 1f / 1f;
|
||||
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
|
||||
|
||||
foreach (var (coord, value) in TrackedCoordinates)
|
||||
{
|
||||
if (lit && value.Visible)
|
||||
{
|
||||
var mapPos = coord.ToMap(_entManager);
|
||||
|
||||
if (mapPos.MapId != MapId.Nullspace)
|
||||
{
|
||||
var position = xform.InvWorldMatrix.Transform(mapPos.Position) - offset;
|
||||
position = Scale(new Vector2(position.X, -position.Y));
|
||||
|
||||
handle.DrawCircle(position, MinimapScale / 2f, value.Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 Scale(Vector2 position)
|
||||
{
|
||||
return position * MinimapScale + MidPoint;
|
||||
}
|
||||
}
|
||||
35
Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
public sealed class StationMapBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private StationMapWindow? _window;
|
||||
|
||||
public StationMapBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window?.Close();
|
||||
EntityUid? gridUid = null;
|
||||
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<TransformComponent>(Owner.Owner, out var xform))
|
||||
{
|
||||
gridUid = xform.GridUid;
|
||||
}
|
||||
|
||||
_window = new StationMapWindow(gridUid, Owner.Owner);
|
||||
_window.OpenCentered();
|
||||
_window.OnClose += Close;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
7
Content.Client/Pinpointer/UI/StationMapWindow.xaml
Normal file
@@ -0,0 +1,7 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
Title="{Loc 'station-map-window-title'}"
|
||||
Resizable="False">
|
||||
<ui:NavMapControl Name="NavMapScreen"/>
|
||||
</controls:FancyWindow>
|
||||
24
Content.Client/Pinpointer/UI/StationMapWindow.xaml.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StationMapWindow : FancyWindow
|
||||
{
|
||||
public StationMapWindow(EntityUid? mapUid, EntityUid? trackedEntity)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
NavMapScreen.MapUid = mapUid;
|
||||
|
||||
if (trackedEntity != null)
|
||||
NavMapScreen.TrackedCoordinates.Add(new EntityCoordinates(trackedEntity.Value, Vector2.Zero), (true, Color.Red));
|
||||
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<MetaDataComponent>(mapUid, out var metadata))
|
||||
{
|
||||
Title = metadata.EntityName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -20,11 +21,9 @@ public class DockingControl : Control
|
||||
private float _rangeSquared = 0f;
|
||||
private const float GridLinesDistance = 32f;
|
||||
|
||||
private int MinimapRadius => (int) Math.Min(Size.X, Size.Y) / 2;
|
||||
|
||||
private Vector2 MidPoint => (Size / 2) * UIScale;
|
||||
private int SizeFull => (int) (MinimapRadius * 2 * UIScale);
|
||||
private int ScaledMinimapRadius => (int) (MinimapRadius * UIScale);
|
||||
private int MidPoint => SizeFull / 2;
|
||||
private int SizeFull => (int) (MapGridControl.UIDisplayRadius * 2 * UIScale);
|
||||
private int ScaledMinimapRadius => (int) (MapGridControl.UIDisplayRadius * UIScale);
|
||||
private float MinimapScale => _range != 0 ? ScaledMinimapRadius / _range : 0f;
|
||||
|
||||
public EntityUid? ViewedDock;
|
||||
@@ -52,8 +51,8 @@ public class DockingControl : Control
|
||||
|
||||
var fakeAA = new Color(0.08f, 0.08f, 0.08f);
|
||||
|
||||
handle.DrawCircle((MidPoint.X, MidPoint.Y), ScaledMinimapRadius + 1, fakeAA);
|
||||
handle.DrawCircle((MidPoint.X, MidPoint.Y), ScaledMinimapRadius, Color.Black);
|
||||
handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius + 1, fakeAA);
|
||||
handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius, Color.Black);
|
||||
|
||||
var gridLines = new Color(0.08f, 0.08f, 0.08f);
|
||||
var gridLinesRadial = 8;
|
||||
@@ -61,14 +60,14 @@ public class DockingControl : Control
|
||||
|
||||
for (var i = 1; i < gridLinesEquatorial + 1; i++)
|
||||
{
|
||||
handle.DrawCircle((MidPoint.X, MidPoint.Y), GridLinesDistance * MinimapScale * i, gridLines, false);
|
||||
handle.DrawCircle((MidPoint, MidPoint), GridLinesDistance * MinimapScale * i, gridLines, false);
|
||||
}
|
||||
|
||||
for (var i = 0; i < gridLinesRadial; i++)
|
||||
{
|
||||
Angle angle = (Math.PI / gridLinesRadial) * i;
|
||||
var aExtent = angle.ToVec() * ScaledMinimapRadius;
|
||||
handle.DrawLine((MidPoint.X, MidPoint.Y) - aExtent, (MidPoint.X, MidPoint.Y) + aExtent, gridLines);
|
||||
handle.DrawLine((MidPoint, MidPoint) - aExtent, (MidPoint, MidPoint) + aExtent, gridLines);
|
||||
}
|
||||
|
||||
if (Coordinates == null ||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using JetBrains.Annotations;
|
||||
@@ -13,7 +14,6 @@ using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Shuttles.UI;
|
||||
@@ -21,13 +21,11 @@ namespace Content.Client.Shuttles.UI;
|
||||
/// <summary>
|
||||
/// Displays nearby grids inside of a control.
|
||||
/// </summary>
|
||||
public sealed class RadarControl : Control
|
||||
public sealed class RadarControl : MapGridControl
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private const float ScrollSensitivity = 8f;
|
||||
private const float GridLinesDistance = 32f;
|
||||
|
||||
/// <summary>
|
||||
@@ -37,26 +35,6 @@ public sealed class RadarControl : Control
|
||||
|
||||
private Angle? _rotation;
|
||||
|
||||
private float _radarMinRange = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
private float _radarMaxRange = SharedRadarConsoleSystem.DefaultMaxRange;
|
||||
public float RadarRange { get; private set; } = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
|
||||
/// <summary>
|
||||
/// We'll lerp between the radarrange and actual range
|
||||
/// </summary>
|
||||
private float _actualRadarRange = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the maximum distance that IFF labels will display.
|
||||
/// </summary>
|
||||
public float MaxRadarRange { get; private set; } = 256f * 10f;
|
||||
|
||||
private int MinimapRadius => (int) Math.Min(Size.X, Size.Y) / 2;
|
||||
private Vector2 MidPoint => (Size / 2) * UIScale;
|
||||
private int SizeFull => (int) (MinimapRadius * 2 * UIScale);
|
||||
private int ScaledMinimapRadius => (int) (MinimapRadius * UIScale);
|
||||
private float MinimapScale => RadarRange != 0 ? ScaledMinimapRadius / RadarRange : 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Shows a label on each radar object.
|
||||
/// </summary>
|
||||
@@ -79,11 +57,9 @@ public sealed class RadarControl : Control
|
||||
/// </summary>
|
||||
public Action<EntityCoordinates>? OnRadarClick;
|
||||
|
||||
public RadarControl()
|
||||
public RadarControl() : base(64f, 256f, 256f)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
MinSize = (SizeFull, SizeFull);
|
||||
RectClipContent = true;
|
||||
|
||||
}
|
||||
|
||||
public void SetMatrix(EntityCoordinates? coordinates, Angle? angle)
|
||||
@@ -92,25 +68,6 @@ public sealed class RadarControl : Control
|
||||
_rotation = angle;
|
||||
}
|
||||
|
||||
public void UpdateState(RadarConsoleBoundInterfaceState ls)
|
||||
{
|
||||
_radarMaxRange = ls.MaxRange;
|
||||
|
||||
if (_radarMaxRange < _radarMinRange)
|
||||
_radarMinRange = _radarMaxRange;
|
||||
|
||||
_actualRadarRange = Math.Clamp(_actualRadarRange, _radarMinRange, _radarMaxRange);
|
||||
|
||||
_docks.Clear();
|
||||
|
||||
foreach (var state in ls.Docks)
|
||||
{
|
||||
var coordinates = state.Coordinates;
|
||||
var grid = _docks.GetOrNew(coordinates.EntityId);
|
||||
grid.Add(state);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
@@ -148,32 +105,38 @@ public sealed class RadarControl : Control
|
||||
return coords;
|
||||
}
|
||||
|
||||
protected override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
public void UpdateState(RadarConsoleBoundInterfaceState ls)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
AddRadarRange(-args.Delta.Y * 1f / ScrollSensitivity * RadarRange);
|
||||
}
|
||||
WorldMaxRange = ls.MaxRange;
|
||||
|
||||
public void AddRadarRange(float value)
|
||||
{
|
||||
_actualRadarRange = Math.Clamp(_actualRadarRange + value, _radarMinRange, _radarMaxRange);
|
||||
if (WorldMaxRange < WorldRange)
|
||||
{
|
||||
ActualRadarRange = WorldMaxRange;
|
||||
}
|
||||
|
||||
if (WorldMaxRange < WorldMinRange)
|
||||
WorldMinRange = WorldMaxRange;
|
||||
|
||||
ActualRadarRange = Math.Clamp(ActualRadarRange, WorldMinRange, WorldMaxRange);
|
||||
|
||||
_docks.Clear();
|
||||
|
||||
foreach (var state in ls.Docks)
|
||||
{
|
||||
var coordinates = state.Coordinates;
|
||||
var grid = _docks.GetOrNew(coordinates.EntityId);
|
||||
grid.Add(state);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
if (!_actualRadarRange.Equals(RadarRange))
|
||||
{
|
||||
var diff = _actualRadarRange - RadarRange;
|
||||
var lerpRate = 10f;
|
||||
|
||||
RadarRange += (float) Math.Clamp(diff, -lerpRate * MathF.Abs(diff) * _timing.FrameTime.TotalSeconds, lerpRate * MathF.Abs(diff) * _timing.FrameTime.TotalSeconds);
|
||||
OnRadarRangeChanged?.Invoke(RadarRange);
|
||||
}
|
||||
base.Draw(handle);
|
||||
|
||||
var fakeAA = new Color(0.08f, 0.08f, 0.08f);
|
||||
|
||||
handle.DrawCircle((MidPoint.X, MidPoint.Y), ScaledMinimapRadius + 1, fakeAA);
|
||||
handle.DrawCircle((MidPoint.X, MidPoint.Y), ScaledMinimapRadius, Color.Black);
|
||||
handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius + 1, fakeAA);
|
||||
handle.DrawCircle((MidPoint, MidPoint), ScaledMinimapRadius, Color.Black);
|
||||
|
||||
// No data
|
||||
if (_coordinates == null || _rotation == null)
|
||||
@@ -184,18 +147,18 @@ public sealed class RadarControl : Control
|
||||
|
||||
var gridLines = new Color(0.08f, 0.08f, 0.08f);
|
||||
var gridLinesRadial = 8;
|
||||
var gridLinesEquatorial = (int) Math.Floor(RadarRange / GridLinesDistance);
|
||||
var gridLinesEquatorial = (int) Math.Floor(WorldRange / GridLinesDistance);
|
||||
|
||||
for (var i = 1; i < gridLinesEquatorial + 1; i++)
|
||||
{
|
||||
handle.DrawCircle((MidPoint.X, MidPoint.Y), GridLinesDistance * MinimapScale * i, gridLines, false);
|
||||
handle.DrawCircle((MidPoint, MidPoint), GridLinesDistance * MinimapScale * i, gridLines, false);
|
||||
}
|
||||
|
||||
for (var i = 0; i < gridLinesRadial; i++)
|
||||
{
|
||||
Angle angle = (Math.PI / gridLinesRadial) * i;
|
||||
var aExtent = angle.ToVec() * ScaledMinimapRadius;
|
||||
handle.DrawLine((MidPoint.X, MidPoint.Y) - aExtent, (MidPoint.X, MidPoint.Y) + aExtent, gridLines);
|
||||
handle.DrawLine((MidPoint, MidPoint) - aExtent, (MidPoint, MidPoint) + aExtent, gridLines);
|
||||
}
|
||||
|
||||
var metaQuery = _entManager.GetEntityQuery<MetaDataComponent>();
|
||||
@@ -368,7 +331,7 @@ public sealed class RadarControl : Control
|
||||
var position = state.Coordinates.Position;
|
||||
var uiPosition = matrix.Transform(position);
|
||||
|
||||
if (uiPosition.Length > RadarRange - DockScale) continue;
|
||||
if (uiPosition.Length > WorldRange - DockScale) continue;
|
||||
|
||||
var color = HighlightedDock == ent ? state.HighlightedColor : state.Color;
|
||||
|
||||
@@ -446,7 +409,7 @@ public sealed class RadarControl : Control
|
||||
var adjustedStart = matrix.Transform(start);
|
||||
var adjustedEnd = matrix.Transform(end);
|
||||
|
||||
if (adjustedStart.Length > RadarRange || adjustedEnd.Length > RadarRange)
|
||||
if (adjustedStart.Length > ActualRadarRange || adjustedEnd.Length > ActualRadarRange)
|
||||
continue;
|
||||
|
||||
start = ScalePosition(new Vector2(adjustedStart.X, -adjustedStart.Y));
|
||||
|
||||
@@ -52,8 +52,8 @@ public sealed partial class ShuttleConsoleWindow : FancyWindow,
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_timing = IoCManager.Resolve<IGameTiming>();
|
||||
|
||||
OnRadarRangeChange(RadarScreen.RadarRange);
|
||||
RadarScreen.OnRadarRangeChanged += OnRadarRangeChange;
|
||||
WorldRangeChange(RadarScreen.WorldRange);
|
||||
RadarScreen.WorldRangeChanged += WorldRangeChange;
|
||||
|
||||
IFFToggle.OnToggled += OnIFFTogglePressed;
|
||||
IFFToggle.Pressed = RadarScreen.ShowIFF;
|
||||
@@ -64,7 +64,7 @@ public sealed partial class ShuttleConsoleWindow : FancyWindow,
|
||||
UndockButton.OnPressed += OnUndockPressed;
|
||||
}
|
||||
|
||||
private void OnRadarRangeChange(float value)
|
||||
private void WorldRangeChange(float value)
|
||||
{
|
||||
RadarRange.Text = $"{value:0}";
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@ namespace Content.Client.Stylesheets
|
||||
public const string StyleClassPopupMessageLarge = "PopupMessageLarge";
|
||||
public const string StyleClassPopupMessageLargeCaution = "PopupMessageLargeCaution";
|
||||
|
||||
public static readonly Color PanelDark = Color.FromHex("#1E1E22");
|
||||
|
||||
public static readonly Color NanoGold = Color.FromHex("#A88B5E");
|
||||
public static readonly Color GoodGreenFore = Color.FromHex("#31843E");
|
||||
public static readonly Color ConcerningOrangeFore = Color.FromHex("#A5762F");
|
||||
@@ -453,7 +455,7 @@ namespace Content.Client.Stylesheets
|
||||
var sliderBackBox = new StyleBoxTexture
|
||||
{
|
||||
Texture = sliderFillTex,
|
||||
Modulate = Color.FromHex("#1E1E22")
|
||||
Modulate = PanelDark,
|
||||
};
|
||||
|
||||
var sliderForeBox = new StyleBoxTexture
|
||||
|
||||
79
Content.Client/UserInterface/Controls/MapGridControl.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Handles generic grid-drawing data, with zoom and dragging.
|
||||
/// </summary>
|
||||
public abstract class MapGridControl : Control
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
|
||||
protected const float ScrollSensitivity = 8f;
|
||||
|
||||
/// <summary>
|
||||
/// UI pixel radius.
|
||||
/// </summary>
|
||||
public const int UIDisplayRadius = 320;
|
||||
protected const int MinimapMargin = 4;
|
||||
|
||||
protected float WorldMinRange;
|
||||
protected float WorldMaxRange;
|
||||
public float WorldRange;
|
||||
|
||||
/// <summary>
|
||||
/// We'll lerp between the radarrange and actual range
|
||||
/// </summary>
|
||||
protected float ActualRadarRange;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the maximum distance that will display.
|
||||
/// </summary>
|
||||
public float MaxRadarRange { get; private set; } = 256f * 10f;
|
||||
|
||||
protected int MidPoint => SizeFull / 2;
|
||||
protected int SizeFull => (int) ((UIDisplayRadius + MinimapMargin) * 2 * UIScale);
|
||||
protected int ScaledMinimapRadius => (int) (UIDisplayRadius * UIScale);
|
||||
protected float MinimapScale => WorldRange != 0 ? ScaledMinimapRadius / WorldRange : 0f;
|
||||
|
||||
public event Action<float>? WorldRangeChanged;
|
||||
|
||||
public MapGridControl(float minRange, float maxRange, float range)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
SetSize = (SizeFull, SizeFull);
|
||||
RectClipContent = true;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
ActualRadarRange = WorldRange;
|
||||
WorldMinRange = minRange;
|
||||
WorldMaxRange = maxRange;
|
||||
WorldRange = range;
|
||||
ActualRadarRange = range;
|
||||
}
|
||||
|
||||
protected override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
AddRadarRange(-args.Delta.Y * 1f / ScrollSensitivity * ActualRadarRange);
|
||||
}
|
||||
|
||||
public void AddRadarRange(float value)
|
||||
{
|
||||
ActualRadarRange = Math.Clamp(ActualRadarRange + value, WorldMinRange, WorldMaxRange);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
if (!ActualRadarRange.Equals(WorldRange))
|
||||
{
|
||||
var diff = ActualRadarRange - WorldRange;
|
||||
const float lerpRate = 10f;
|
||||
|
||||
WorldRange += (float) Math.Clamp(diff, -lerpRate * MathF.Abs(diff) * Timing.FrameTime.TotalSeconds, lerpRate * MathF.Abs(diff) * Timing.FrameTime.TotalSeconds);
|
||||
WorldRangeChanged?.Invoke(WorldRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,9 +60,8 @@ namespace Content.Server.Medical.CrewMonitoring
|
||||
return;
|
||||
|
||||
// update all sensors info
|
||||
var xform = Transform(uid);
|
||||
var allSensors = component.ConnectedSensors.Values.ToList();
|
||||
var uiState = new CrewMonitoringState(allSensors, xform.WorldPosition, component.Snap, component.Precision);
|
||||
var uiState = new CrewMonitoringState(allSensors, component.Snap, component.Precision);
|
||||
ui.SetState(uiState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Medical.SuitSensors
|
||||
{
|
||||
@@ -53,9 +54,10 @@ namespace Content.Server.Medical.SuitSensors
|
||||
public EntityUid? User = null;
|
||||
|
||||
/// <summary>
|
||||
/// Last time when sensor updated owners status
|
||||
/// Next time when sensor updated owners status
|
||||
/// </summary>
|
||||
public TimeSpan LastUpdate = TimeSpan.Zero;
|
||||
[DataField("nextUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextUpdate = TimeSpan.Zero;
|
||||
|
||||
/// <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.
|
||||
|
||||
@@ -21,23 +21,21 @@ namespace Content.Server.Medical.SuitSensors
|
||||
{
|
||||
public sealed class SuitSensorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[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 DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly CrewMonitoringServerSystem _monitoringServerSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
private const float UpdateRate = 1f;
|
||||
private float _updateDif;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SuitSensorComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SuitSensorComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||
SubscribeLocalEvent<SuitSensorComponent, GotEquippedEvent>(OnEquipped);
|
||||
SubscribeLocalEvent<SuitSensorComponent, GotUnequippedEvent>(OnUnequipped);
|
||||
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
|
||||
@@ -46,30 +44,29 @@ namespace Content.Server.Medical.SuitSensors
|
||||
SubscribeLocalEvent<SuitSensorComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||
}
|
||||
|
||||
private void OnUnpaused(EntityUid uid, SuitSensorComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.NextUpdate += args.PausedTime;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// check update rate
|
||||
_updateDif += frameTime;
|
||||
if (_updateDif < UpdateRate)
|
||||
return;
|
||||
|
||||
_updateDif -= UpdateRate;
|
||||
|
||||
var curTime = _gameTiming.CurTime;
|
||||
var sensors = EntityManager.EntityQuery<SuitSensorComponent, DeviceNetworkComponent>();
|
||||
foreach (var (sensor, device) in sensors)
|
||||
var sensors = EntityManager.EntityQueryEnumerator<SuitSensorComponent, DeviceNetworkComponent>();
|
||||
|
||||
while (sensors.MoveNext(out var sensor, out var device))
|
||||
{
|
||||
if (!device.TransmitFrequency.HasValue || !sensor.StationId.HasValue)
|
||||
if (device.TransmitFrequency is null || !sensor.StationId.HasValue)
|
||||
continue;
|
||||
|
||||
// check if sensor is ready to update
|
||||
if (curTime - sensor.LastUpdate < sensor.UpdateRate)
|
||||
if (curTime < sensor.NextUpdate)
|
||||
continue;
|
||||
|
||||
// Add a random offset to the next update time that isn't longer than the sensors update rate
|
||||
sensor.LastUpdate = curTime.Add(TimeSpan.FromSeconds(_random.Next(0, sensor.UpdateRate.Seconds)));
|
||||
// TODO: This would cause imprecision at different tick rates.
|
||||
sensor.NextUpdate = curTime + sensor.UpdateRate;
|
||||
|
||||
// get sensor status
|
||||
var status = GetSensorState(sensor.Owner, sensor);
|
||||
@@ -278,9 +275,6 @@ namespace Content.Server.Medical.SuitSensors
|
||||
totalDamage = damageable.TotalDamage.Int();
|
||||
|
||||
// finally, form suit sensor status
|
||||
var xForm = Transform(sensor.User.Value);
|
||||
var xFormQuery = GetEntityQuery<TransformComponent>();
|
||||
var coords = _xform.GetMoverCoordinates(xForm, xFormQuery);
|
||||
var status = new SuitSensorStatus(userName, userJob);
|
||||
switch (sensor.Mode)
|
||||
{
|
||||
@@ -294,7 +288,26 @@ namespace Content.Server.Medical.SuitSensors
|
||||
case SuitSensorMode.SensorCords:
|
||||
status.IsAlive = isAlive;
|
||||
status.TotalDamage = totalDamage;
|
||||
status.Coordinates = coords;
|
||||
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 = coordinates;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -317,7 +330,7 @@ namespace Content.Server.Medical.SuitSensors
|
||||
if (status.TotalDamage != null)
|
||||
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
|
||||
if (status.Coordinates != null)
|
||||
payload.Add(SuitSensorConstants.NET_CORDINATES, status.Coordinates);
|
||||
payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates);
|
||||
|
||||
|
||||
return payload;
|
||||
@@ -341,7 +354,7 @@ namespace Content.Server.Medical.SuitSensors
|
||||
|
||||
// try get total damage and cords (optionals)
|
||||
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
|
||||
payload.TryGetValue(SuitSensorConstants.NET_CORDINATES, out EntityCoordinates? cords);
|
||||
payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out EntityCoordinates? cords);
|
||||
|
||||
var status = new SuitSensorStatus(name, job)
|
||||
{
|
||||
|
||||
158
Content.Server/Pinpointer/NavMapSystem.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using Content.Shared.Pinpointer;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Content.Server.Pinpointer;
|
||||
|
||||
/// <summary>
|
||||
/// Handles data to be used for in-grid map displays.
|
||||
/// </summary>
|
||||
public sealed class NavMapSystem : SharedNavMapSystem
|
||||
{
|
||||
[Dependency] private readonly TagSystem _tags = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<AnchorStateChangedEvent>(OnAnchorChange);
|
||||
SubscribeLocalEvent<ReAnchorEvent>(OnReAnchor);
|
||||
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<NavMapComponent, GridSplitEvent>(OnNavMapSplit);
|
||||
}
|
||||
|
||||
private void OnNavMapSplit(EntityUid uid, NavMapComponent component, ref GridSplitEvent args)
|
||||
{
|
||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var tagQuery = GetEntityQuery<TagComponent>();
|
||||
var gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
|
||||
foreach (var grid in args.NewGrids)
|
||||
{
|
||||
var newComp = EnsureComp<NavMapComponent>(grid);
|
||||
RefreshGrid(newComp, gridQuery.GetComponent(grid), physicsQuery, tagQuery);
|
||||
}
|
||||
|
||||
RefreshGrid(component, gridQuery.GetComponent(uid), physicsQuery, tagQuery);
|
||||
}
|
||||
|
||||
private void RefreshGrid(NavMapComponent component, MapGridComponent grid, EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TagComponent> tagQuery)
|
||||
{
|
||||
component.Chunks.Clear();
|
||||
|
||||
var tiles = grid.GetAllTilesEnumerator();
|
||||
|
||||
while (tiles.MoveNext(out var tile))
|
||||
{
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.Value.GridIndices, ChunkSize);
|
||||
|
||||
if (!component.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
{
|
||||
chunk = new NavMapChunk(chunkOrigin);
|
||||
}
|
||||
|
||||
RefreshTile(grid, component, chunk, tile.Value.GridIndices, physicsQuery, tagQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
||||
{
|
||||
var data = new Dictionary<Vector2i, int>(component.Chunks.Count);
|
||||
foreach (var (index, chunk) in component.Chunks)
|
||||
{
|
||||
data.Add(index, chunk.TileData);
|
||||
}
|
||||
|
||||
// TODO: Diffs
|
||||
args.State = new NavMapComponentState()
|
||||
{
|
||||
TileData = data,
|
||||
};
|
||||
}
|
||||
|
||||
private void OnReAnchor(ref ReAnchorEvent ev)
|
||||
{
|
||||
if (TryComp<MapGridComponent>(ev.OldGrid, out var oldGrid) &&
|
||||
TryComp<NavMapComponent>(ev.OldGrid, out var navMap))
|
||||
{
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(ev.TilePos, ChunkSize);
|
||||
|
||||
if (navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
{
|
||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var tagQuery = GetEntityQuery<TagComponent>();
|
||||
RefreshTile(oldGrid, navMap, chunk, ev.TilePos, physicsQuery, tagQuery);
|
||||
}
|
||||
}
|
||||
|
||||
HandleAnchor(ev.Xform);
|
||||
}
|
||||
|
||||
private void OnAnchorChange(ref AnchorStateChangedEvent ev)
|
||||
{
|
||||
HandleAnchor(ev.Transform);
|
||||
}
|
||||
|
||||
private void HandleAnchor(TransformComponent xform)
|
||||
{
|
||||
if (!TryComp<NavMapComponent>(xform.GridUid, out var navMap) ||
|
||||
!TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
||||
return;
|
||||
|
||||
var tile = grid.LocalToTile(xform.Coordinates);
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var tagQuery = GetEntityQuery<TagComponent>();
|
||||
|
||||
if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
{
|
||||
chunk = new NavMapChunk(chunkOrigin);
|
||||
navMap.Chunks[chunkOrigin] = chunk;
|
||||
}
|
||||
|
||||
RefreshTile(grid, navMap, chunk, tile, physicsQuery, tagQuery);
|
||||
}
|
||||
|
||||
private void RefreshTile(MapGridComponent grid, NavMapComponent component, NavMapChunk chunk, Vector2i tile,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
EntityQuery<TagComponent> tagQuery)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
|
||||
var existing = chunk.TileData;
|
||||
var flag = GetFlag(relative);
|
||||
|
||||
chunk.TileData &= ~flag;
|
||||
|
||||
var enumerator = grid.GetAnchoredEntitiesEnumerator(tile);
|
||||
// TODO: Use something to get convex poly.
|
||||
|
||||
while (enumerator.MoveNext(out var ent))
|
||||
{
|
||||
if (!physicsQuery.TryGetComponent(ent, out var body) ||
|
||||
!body.CanCollide ||
|
||||
!body.Hard ||
|
||||
body.BodyType != BodyType.Static ||
|
||||
(!_tags.HasTag(ent.Value, "Wall", tagQuery) &&
|
||||
!_tags.HasTag(ent.Value, "Window", tagQuery)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
chunk.TileData |= flag;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk.TileData == 0)
|
||||
{
|
||||
component.Chunks.Remove(chunk.Origin);
|
||||
}
|
||||
|
||||
if (existing == chunk.TileData)
|
||||
return;
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Content.Server.Shuttles.Events;
|
||||
|
||||
namespace Content.Server.Pinpointer
|
||||
{
|
||||
public sealed class ServerPinpointerSystem : SharedPinpointerSystem
|
||||
public sealed class PinpointerSystem : SharedPinpointerSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
17
Content.Server/Pinpointer/StationMapComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Content.Server.Pinpointer;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class StationMapComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Added to an entity using station map so when its parent changes we reset it.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class StationMapUserComponent : Component
|
||||
{
|
||||
[DataField("mapUid")]
|
||||
public EntityUid Map;
|
||||
}
|
||||
43
Content.Server/Pinpointer/StationMapSystem.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Pinpointer;
|
||||
|
||||
public sealed class StationMapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<StationMapUserComponent, EntParentChangedMessage>(OnUserParentChanged);
|
||||
SubscribeLocalEvent<StationMapComponent, BoundUIOpenedEvent>(OnStationMapOpened);
|
||||
SubscribeLocalEvent<StationMapComponent, BoundUIClosedEvent>(OnStationMapClosed);
|
||||
}
|
||||
|
||||
private void OnStationMapClosed(EntityUid uid, StationMapComponent component, BoundUIClosedEvent args)
|
||||
{
|
||||
if (!Equals(args.UiKey, StationMapUiKey.Key) || args.Session.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
RemCompDeferred<StationMapUserComponent>(args.Session.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
private void OnUserParentChanged(EntityUid uid, StationMapUserComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
if (TryComp<ActorComponent>(uid, out var actor))
|
||||
{
|
||||
_ui.TryClose(component.Map, StationMapUiKey.Key, actor.PlayerSession);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStationMapOpened(EntityUid uid, StationMapComponent component, BoundUIOpenedEvent args)
|
||||
{
|
||||
if (args.Session.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
var comp = EnsureComp<StationMapUserComponent>(args.Session.AttachedEntity.Value);
|
||||
comp.Map = uid;
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,12 @@ namespace Content.Shared.Medical.CrewMonitoring
|
||||
public sealed class CrewMonitoringState : BoundUserInterfaceState
|
||||
{
|
||||
public List<SuitSensorStatus> Sensors;
|
||||
public readonly Vector2 WorldPosition;
|
||||
public readonly bool Snap;
|
||||
public readonly float Precision;
|
||||
|
||||
public CrewMonitoringState(List<SuitSensorStatus> sensors, Vector2 worldPosition, bool snap, float precision)
|
||||
public CrewMonitoringState(List<SuitSensorStatus> sensors, bool snap, float precision)
|
||||
{
|
||||
Sensors = sensors;
|
||||
WorldPosition = worldPosition;
|
||||
Snap = snap;
|
||||
Precision = precision;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Shared.Medical.SuitSensor
|
||||
public const string NET_JOB = "job";
|
||||
public const string NET_IS_ALIVE = "alive";
|
||||
public const string NET_TOTAL_DAMAGE = "vitals";
|
||||
public const string NET_CORDINATES = "cords";
|
||||
public const string NET_COORDINATES = "coords";
|
||||
|
||||
///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";
|
||||
|
||||
29
Content.Shared/Pinpointer/NavMapComponent.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Pinpointer;
|
||||
|
||||
/// <summary>
|
||||
/// Used to store grid poly data to be used for UIs.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class NavMapComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<Vector2i, NavMapChunk> Chunks = new();
|
||||
}
|
||||
|
||||
public sealed class NavMapChunk
|
||||
{
|
||||
public readonly Vector2i Origin;
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask for tiles, 1 for occupied and 0 for empty.
|
||||
/// </summary>
|
||||
public int TileData;
|
||||
|
||||
public NavMapChunk(Vector2i origin)
|
||||
{
|
||||
Origin = origin;
|
||||
}
|
||||
}
|
||||
45
Content.Shared/Pinpointer/SharedNavMapSystem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Pinpointer;
|
||||
|
||||
public abstract class SharedNavMapSystem : EntitySystem
|
||||
{
|
||||
public const byte ChunkSize = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Converts the chunk's tile into a bitflag for the slot.
|
||||
/// </summary>
|
||||
public static int GetFlag(Vector2i relativeTile)
|
||||
{
|
||||
return 1 << (relativeTile.X * ChunkSize + relativeTile.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the chunk's tile into a bitflag for the slot.
|
||||
/// </summary>
|
||||
public static Vector2i GetTile(int flag)
|
||||
{
|
||||
var value = Math.Log2(flag);
|
||||
var x = (int) value / ChunkSize;
|
||||
var y = (int) value % ChunkSize;
|
||||
var result = new Vector2i(x, y);
|
||||
|
||||
DebugTools.Assert(GetFlag(result) == flag);
|
||||
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class NavMapComponentState : ComponentState
|
||||
{
|
||||
public Dictionary<Vector2i, int> TileData = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class NavMapDiffComponentState : ComponentState
|
||||
{
|
||||
public Dictionary<Vector2i, int> TileData = new();
|
||||
public List<Vector2i> RemovedChunks = new();
|
||||
}
|
||||
}
|
||||
9
Content.Shared/Pinpointer/SharedStationMapSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Pinpointer;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationMapUiKey : byte
|
||||
{
|
||||
Key,
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Shared.Power
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum PowerDeviceVisuals
|
||||
public enum PowerDeviceVisuals : byte
|
||||
{
|
||||
VisualState,
|
||||
Powered
|
||||
|
||||
1
Resources/Locale/en-US/pinpointer/station_map.ftl
Normal file
@@ -0,0 +1 @@
|
||||
station-map-window-title = Station map
|
||||
@@ -0,0 +1,5 @@
|
||||
- type: entity
|
||||
parent: BaseElectronics
|
||||
id: StationMapCircuitboard
|
||||
name: station map circuit board
|
||||
description: A printed circuit board for a station map.
|
||||
@@ -0,0 +1,33 @@
|
||||
- type: entity
|
||||
id: StationMap
|
||||
name: station map
|
||||
description: Displays a readout of the current station.
|
||||
parent: BaseItem
|
||||
suffix: Handheld
|
||||
components:
|
||||
- type: StationMap
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
sprite: Objects/Devices/tablets.rsi
|
||||
layers:
|
||||
- state: tablet
|
||||
- state: generic
|
||||
shader: unshaded
|
||||
- type: ActivatableUI
|
||||
inHandsOnly: true
|
||||
singleUser: true
|
||||
key: enum.StationMapUiKey.Key
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.StationMapUiKey.Key
|
||||
type: StationMapBoundUserInterface
|
||||
@@ -0,0 +1,76 @@
|
||||
- type: entity
|
||||
id: WallStationMapBroken
|
||||
name: station map
|
||||
description: A virtual map of the surrounding station.
|
||||
suffix: Wall broken
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
components:
|
||||
- type: InteractionOutline
|
||||
- type: Clickable
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
sprite: Structures/Machines/station_map.rsi
|
||||
drawdepth: WallMountedItems
|
||||
layers:
|
||||
- state: station_map_broken
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: GlassBreak
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
|
||||
- type: entity
|
||||
id: WallStationMap
|
||||
name: station map
|
||||
parent: WallStationMapBroken
|
||||
suffix: Wall
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
components:
|
||||
- type: StationMap
|
||||
- type: Transform
|
||||
anchored: true
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: station_map0
|
||||
- state: unshaded
|
||||
map: [ "enum.PowerDeviceVisualLayers.Powered" ]
|
||||
shader: unshaded
|
||||
- type: ApcPowerReceiver
|
||||
powerLoad: 200
|
||||
priority: Low
|
||||
- type: WallMount
|
||||
arc: 360
|
||||
- type: ExtensionCableReceiver
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: ActivatableUI
|
||||
key: enum.StationMapUiKey.Key
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: GlassBreak
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
WallStationMapBroken:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.StationMapUiKey.Key
|
||||
type: StationMapBoundUserInterface
|
||||
BIN
Resources/Textures/Objects/Devices/tablets.rsi/generic.png
Normal file
|
After Width: | Height: | Size: 271 B |
1
Resources/Textures/Objects/Devices/tablets.rsi/meta.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version":1,"license":"CC-BY-SA-3.0","copyright":"https://github.com/Baystation12/Baystation12/tree/17e84546562b97d775cb26790a08e6d87e7f8077","size":{"x":32,"y":32},"states":[{"name":"tablet"},{"name":"tabletsol"},{"name":"generic","delays":[[0.3,0.3]]}]}
|
||||
BIN
Resources/Textures/Objects/Devices/tablets.rsi/tablet.png
Normal file
|
After Width: | Height: | Size: 348 B |
BIN
Resources/Textures/Objects/Devices/tablets.rsi/tabletsol.png
Normal file
|
After Width: | Height: | Size: 414 B |
@@ -0,0 +1 @@
|
||||
{"version":1,"license":"CC-BY-SA-3.0","copyright":"https://github.com/vgstation-coders/vgstation13/tree/c5d2df28ee81ca4c7d57f4a2a3dd548a7995c084","size":{"x":32,"y":32},"states":[{"name":"station_map_frame0"},{"name":"station_map_frame1"},{"name":"station_map_frame2"},{"name":"station_map_frame3"},{"name":"unshaded"},{"name":"station_map_broken"},{"name":"station_map0"},{"name":"station_map-panel"}]}
|
||||
|
After Width: | Height: | Size: 389 B |
|
After Width: | Height: | Size: 484 B |
|
After Width: | Height: | Size: 687 B |
|
After Width: | Height: | Size: 357 B |
|
After Width: | Height: | Size: 416 B |
|
After Width: | Height: | Size: 639 B |
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 6.2 KiB |