diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs index 6556aeaf99..35b0a32b56 100644 --- a/Content.Client/Pinpointer/NavMapSystem.cs +++ b/Content.Client/Pinpointer/NavMapSystem.cs @@ -29,6 +29,9 @@ public sealed class NavMapSystem : SharedNavMapSystem TileData = data, }); } + + component.Beacons.Clear(); + component.Beacons.AddRange(state.Beacons); } } diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index bd61587e8b..04d8cc76f9 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -2,7 +2,9 @@ using System.Numerics; using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; using Content.Shared.Pinpointer; +using Robust.Client.GameObjects; using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; @@ -20,18 +22,19 @@ namespace Content.Client.Pinpointer.UI; public sealed class NavMapControl : MapGridControl { [Dependency] private readonly IEntityManager _entManager = default!; + private SharedTransformSystem _transform; public EntityUid? MapUid; - public Dictionary TrackedCoordinates = new(); private Vector2 _offset; private bool _draggin; - private bool _recentering = false; - - private float _recenterMinimum = 0.05f; + private readonly float _recenterMinimum = 0.05f; + private readonly Font _font; + private static readonly Color TileColor = new(30, 67, 30); + private static readonly Color BeaconColor = Color.FromSrgb(TileColor.WithAlpha(0.8f)); // TODO: https://github.com/space-wizards/RobustToolbox/issues/3818 private readonly Label _zoom = new() @@ -52,6 +55,11 @@ public sealed class NavMapControl : MapGridControl public NavMapControl() : base(8f, 128f, 48f) { IoCManager.InjectDependencies(this); + + _transform = _entManager.System(); + var cache = IoCManager.Resolve(); + _font = new VectorFont(cache.GetResource("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 16); + RectClipContent = true; HorizontalExpand = true; VerticalExpand = true; @@ -175,7 +183,6 @@ public sealed class NavMapControl : MapGridControl } var offset = _offset; - var tileColor = new Color(30, 67, 30); var lineColor = new Color(102, 217, 102); if (_entManager.TryGetComponent(MapUid, out var physics)) @@ -200,7 +207,7 @@ public sealed class NavMapControl : MapGridControl verts[i] = Scale(new Vector2(vert.X, -vert.Y)); } - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], tileColor); + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], TileColor); } } @@ -333,6 +340,24 @@ public sealed class NavMapControl : MapGridControl } } } + + // Beacons + var labelOffset = new Vector2(0.5f, 0.5f) * MinimapScale; + var rectBuffer = new Vector2(5f, 3f); + + foreach (var beacon in navMap.Beacons) + { + var position = beacon.Position - offset; + + position = Scale(position with { Y = -position.Y }); + + handle.DrawCircle(position, MinimapScale / 2f, beacon.Color); + var textDimensions = handle.GetDimensions(_font, beacon.Text, 1f); + + var labelPosition = position + labelOffset; + handle.DrawRect(new UIBox2(labelPosition, labelPosition + textDimensions + rectBuffer * 2), BeaconColor); + handle.DrawString(_font, labelPosition + rectBuffer, beacon.Text, beacon.Color); + } } private Vector2 Scale(Vector2 position) diff --git a/Content.Server/Pinpointer/NavMapSystem.cs b/Content.Server/Pinpointer/NavMapSystem.cs index dae3c1ca66..140f016558 100644 --- a/Content.Server/Pinpointer/NavMapSystem.cs +++ b/Content.Server/Pinpointer/NavMapSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Station.Components; using Content.Server.Station.Systems; +using Content.Server.Warps; using Content.Shared.Pinpointer; using Content.Shared.Tag; using Robust.Shared.GameStates; @@ -16,40 +17,87 @@ public sealed class NavMapSystem : SharedNavMapSystem { [Dependency] private readonly TagSystem _tags = default!; + private EntityQuery _physicsQuery; + private EntityQuery _tagQuery; + public override void Initialize() { base.Initialize(); + + _physicsQuery = GetEntityQuery(); + _tagQuery = GetEntityQuery(); + SubscribeLocalEvent(OnAnchorChange); SubscribeLocalEvent(OnReAnchor); + SubscribeLocalEvent(OnStationInit); + SubscribeLocalEvent(OnNavMapStartup); SubscribeLocalEvent(OnGetState); SubscribeLocalEvent(OnNavMapSplit); - SubscribeLocalEvent(OnStationInit); + + SubscribeLocalEvent(OnNavMapBeaconStartup); + SubscribeLocalEvent(OnNavMapBeaconAnchor); } private void OnStationInit(StationGridAddedEvent ev) { var comp = EnsureComp(ev.GridId); - var physicsQuery = GetEntityQuery(); - var tagQuery = GetEntityQuery(); - RefreshGrid(comp, Comp(ev.GridId), physicsQuery, tagQuery); + RefreshGrid(comp, Comp(ev.GridId)); + } + + private void OnNavMapBeaconStartup(EntityUid uid, NavMapBeaconComponent component, ComponentStartup args) + { + RefreshNavGrid(uid); + } + + private void OnNavMapBeaconAnchor(EntityUid uid, NavMapBeaconComponent component, ref AnchorStateChangedEvent args) + { + RefreshNavGrid(uid); + } + + /// + /// Refreshes the grid for the corresponding beacon. + /// + /// + private void RefreshNavGrid(EntityUid uid) + { + var xform = Transform(uid); + + if (!CanBeacon(uid, xform) || !TryComp(xform.GridUid, out var navMap)) + return; + + Dirty(xform.GridUid.Value, navMap); + } + + private bool CanBeacon(EntityUid uid, TransformComponent? xform = null) + { + if (!Resolve(uid, ref xform)) + return false; + + return xform.GridUid != null && xform.Anchored; + } + + private void OnNavMapStartup(EntityUid uid, NavMapComponent component, ComponentStartup args) + { + if (!TryComp(uid, out var grid)) + return; + + RefreshGrid(component, grid); } private void OnNavMapSplit(EntityUid uid, NavMapComponent component, ref GridSplitEvent args) { - var physicsQuery = GetEntityQuery(); - var tagQuery = GetEntityQuery(); var gridQuery = GetEntityQuery(); foreach (var grid in args.NewGrids) { var newComp = EnsureComp(grid); - RefreshGrid(newComp, gridQuery.GetComponent(grid), physicsQuery, tagQuery); + RefreshGrid(newComp, gridQuery.GetComponent(grid)); } - RefreshGrid(component, gridQuery.GetComponent(uid), physicsQuery, tagQuery); + RefreshGrid(component, gridQuery.GetComponent(uid)); } - private void RefreshGrid(NavMapComponent component, MapGridComponent grid, EntityQuery physicsQuery, EntityQuery tagQuery) + private void RefreshGrid(NavMapComponent component, MapGridComponent grid) { component.Chunks.Clear(); @@ -65,7 +113,7 @@ public sealed class NavMapSystem : SharedNavMapSystem component.Chunks[chunkOrigin] = chunk; } - RefreshTile(grid, component, chunk, tile.Value.GridIndices, physicsQuery, tagQuery); + RefreshTile(grid, component, chunk, tile.Value.GridIndices); } } @@ -77,10 +125,37 @@ public sealed class NavMapSystem : SharedNavMapSystem data.Add(index, chunk.TileData); } + var beaconQuery = AllEntityQuery(); + var beacons = new List(); + + while (beaconQuery.MoveNext(out var beaconUid, out var beacon, out var xform)) + { + if (xform.GridUid != uid || !CanBeacon(beaconUid, xform)) + continue; + + // TODO: Make warp points use metadata name instead. + string? name = beacon.Text; + + if (name == null) + { + if (TryComp(beaconUid, out var warpPoint) && warpPoint.Location != null) + { + name = warpPoint.Location; + } + else + { + name = MetaData(beaconUid).EntityName; + } + } + + beacons.Add(new NavMapBeacon(beacon.Color, name, xform.LocalPosition)); + } + // TODO: Diffs args.State = new NavMapComponentState() { TileData = data, + Beacons = beacons, }; } @@ -93,9 +168,7 @@ public sealed class NavMapSystem : SharedNavMapSystem if (navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) { - var physicsQuery = GetEntityQuery(); - var tagQuery = GetEntityQuery(); - RefreshTile(oldGrid, navMap, chunk, ev.TilePos, physicsQuery, tagQuery); + RefreshTile(oldGrid, navMap, chunk, ev.TilePos); } } @@ -115,8 +188,6 @@ public sealed class NavMapSystem : SharedNavMapSystem var tile = grid.LocalToTile(xform.Coordinates); var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize); - var physicsQuery = GetEntityQuery(); - var tagQuery = GetEntityQuery(); if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) { @@ -124,12 +195,10 @@ public sealed class NavMapSystem : SharedNavMapSystem navMap.Chunks[chunkOrigin] = chunk; } - RefreshTile(grid, navMap, chunk, tile, physicsQuery, tagQuery); + RefreshTile(grid, navMap, chunk, tile); } - private void RefreshTile(MapGridComponent grid, NavMapComponent component, NavMapChunk chunk, Vector2i tile, - EntityQuery physicsQuery, - EntityQuery tagQuery) + private void RefreshTile(MapGridComponent grid, NavMapComponent component, NavMapChunk chunk, Vector2i tile) { var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); @@ -143,12 +212,12 @@ public sealed class NavMapSystem : SharedNavMapSystem while (enumerator.MoveNext(out var ent)) { - if (!physicsQuery.TryGetComponent(ent, out var body) || + 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))) + (!_tags.HasTag(ent.Value, "Wall", _tagQuery) && + !_tags.HasTag(ent.Value, "Window", _tagQuery))) { continue; } diff --git a/Content.Shared/Pinpointer/NavMapBeaconComponent.cs b/Content.Shared/Pinpointer/NavMapBeaconComponent.cs new file mode 100644 index 0000000000..dfe958d1d1 --- /dev/null +++ b/Content.Shared/Pinpointer/NavMapBeaconComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Pinpointer; + +/// +/// Will show a marker on a NavMap. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class NavMapBeaconComponent : Component +{ + /// + /// Defaults to entity name if nothing found. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("text"), AutoNetworkedField] + public string? Text; + + [ViewVariables(VVAccess.ReadWrite), DataField("color"), AutoNetworkedField] + public Color Color = Color.Orange; +} diff --git a/Content.Shared/Pinpointer/NavMapComponent.cs b/Content.Shared/Pinpointer/NavMapComponent.cs index 3291114b5d..86a0beef18 100644 --- a/Content.Shared/Pinpointer/NavMapComponent.cs +++ b/Content.Shared/Pinpointer/NavMapComponent.cs @@ -9,8 +9,14 @@ namespace Content.Shared.Pinpointer; [RegisterComponent, NetworkedComponent] public sealed partial class NavMapComponent : Component { + /* + * Don't need DataFields as this can be reconstructed + */ + [ViewVariables] public readonly Dictionary Chunks = new(); + + [ViewVariables] public readonly List Beacons = new(); } public sealed class NavMapChunk diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index 3601ae9dfa..3f01934a24 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -34,12 +35,10 @@ public abstract class SharedNavMapSystem : EntitySystem protected sealed class NavMapComponentState : ComponentState { public Dictionary TileData = new(); + + public List Beacons = new(); } [Serializable, NetSerializable] - protected sealed class NavMapDiffComponentState : ComponentState - { - public Dictionary TileData = new(); - public List RemovedChunks = new(); - } + public readonly record struct NavMapBeacon(Color Color, string Text, Vector2 Position); } diff --git a/Resources/Prototypes/Entities/Markers/warp_point.yml b/Resources/Prototypes/Entities/Markers/warp_point.yml index 54c532d684..22ec3ccc6f 100644 --- a/Resources/Prototypes/Entities/Markers/warp_point.yml +++ b/Resources/Prototypes/Entities/Markers/warp_point.yml @@ -7,6 +7,16 @@ - type: Sprite state: pink +- type: entity + id: WarpPointBeacon + parent: MarkerBase + name: warp point (beacon) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + - type: entity parent: WarpPoint id: WarpPointBombing @@ -19,3 +29,100 @@ - state: pink - sprite: Objects/Weapons/Bombs/spidercharge.rsi state: icon + +# Departments +- type: entity + id: WarpPointBeaconBar + parent: MarkerBase + name: warp point (bar) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + text: bar + color: "#791500" + +- type: entity + id: WarpPointBeaconCargo + parent: MarkerBase + name: warp point (cargo) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + text: cargo + color: "#A46106" + +- type: entity + id: WarpPointBeaconCommand + parent: MarkerBase + name: warp point (command) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + text: command + color: "#334E6D" + +- type: entity + id: WarpPointBeaconEngineering + parent: MarkerBase + name: warp point (engineering) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + text: engineering + color: "#EFB341" + +- type: entity + id: WarpPointBeaconMedical + parent: MarkerBase + name: warp point (medical) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + text: medical + color: "#52B4E9" + +- type: entity + id: WarpPointBeaconNeutral + parent: MarkerBase + name: warp point (neutral) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + text: neutral + color: "#D4D4D4" + +- type: entity + id: WarpPointBeaconScience + parent: MarkerBase + name: warp point (science) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + text: science + color: "#D381C9" + +- type: entity + id: WarpPointBeaconService + parent: MarkerBase + name: warp point (service) + components: + - type: WarpPoint + - type: Sprite + state: pink + - type: NavMapBeacon + text: service + color: "#9FED58"