diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index e316faf141..134bf7c003 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -283,6 +283,7 @@ namespace Content.Client.Entry "AtmosAlarmable", "FireAlarm", "AirAlarm", + "RadarConsole", "Guardian", "GuardianCreator", "GuardianHost", diff --git a/Content.Client/Radar/RadarConsoleWindow.xaml b/Content.Client/Radar/RadarConsoleWindow.xaml new file mode 100644 index 0000000000..92e25bf7fc --- /dev/null +++ b/Content.Client/Radar/RadarConsoleWindow.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/Content.Client/Radar/RadarConsoleWindow.xaml.cs b/Content.Client/Radar/RadarConsoleWindow.xaml.cs new file mode 100644 index 0000000000..4bc7c47b7a --- /dev/null +++ b/Content.Client/Radar/RadarConsoleWindow.xaml.cs @@ -0,0 +1,113 @@ +using System; +using Content.Client.Computer; +using Content.Shared.Radar; +using JetBrains.Annotations; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Maths; + +namespace Content.Client.Radar; + +[GenerateTypedNameReferences] +public partial class RadarConsoleWindow : DefaultWindow, IComputerWindow +{ + public RadarConsoleWindow() + { + RobustXamlLoader.Load(this); + } + + public void SetupComputerWindow(ComputerBoundUserInterfaceBase cb) + { + + } + + public void UpdateState(RadarConsoleBoundInterfaceState scc) + { + Radar.UpdateState(scc); + } +} + + +public sealed class RadarControl : Control +{ + private float _radarArea = 256f; + + private float RadarCircleRadius => MathF.Max(0, _radarArea - 8) / 2; + + private RadarConsoleBoundInterfaceState _lastState = new(256f, Array.Empty()); + + private float SizeFull => (int) (_radarArea * UIScale); + + public int RadiusCircle => (int) (RadarCircleRadius * UIScale); + + public RadarControl() + { + MinSize = (SizeFull, SizeFull); + } + + public void UpdateState(RadarConsoleBoundInterfaceState ls) + { + if (!_radarArea.Equals(ls.Range * 2)) + { + _radarArea = ls.Range * 2; + MinSize = (SizeFull, SizeFull); + } + + _lastState = ls; + } + + protected override void Draw(DrawingHandleScreen handle) + { + var point = SizeFull / 2; + var fakeAA = new Color(0.08f, 0.08f, 0.08f); + var gridLines = new Color(0.08f, 0.08f, 0.08f); + var gridLinesRadial = 8; + var gridLinesEquatorial = 8; + + handle.DrawCircle((point, point), RadiusCircle + 1, fakeAA); + handle.DrawCircle((point, point), RadiusCircle, Color.Black); + + for (var i = 0; i < gridLinesEquatorial; i++) + { + handle.DrawCircle((point, point), (RadiusCircle / gridLinesEquatorial) * i, gridLines, false); + } + + for (var i = 0; i < gridLinesRadial; i++) + { + Angle angle = (Math.PI / gridLinesRadial) * i; + var aExtent = angle.ToVec() * RadiusCircle; + handle.DrawLine((point, point) - aExtent, (point, point) + aExtent, gridLines); + } + + handle.DrawLine((point, point) + new Vector2(8, 8), (point, point) - new Vector2(0, 8), Color.Yellow); + handle.DrawLine((point, point) + new Vector2(-8, 8), (point, point) - new Vector2(0, 8), Color.Yellow); + + foreach (var obj in _lastState.Objects) + { + if (obj.Position.Length > RadiusCircle - 24) + continue; + + switch (obj.Shape) + { + case RadarObjectShape.CircleFilled: + case RadarObjectShape.Circle: + { + handle.DrawCircle(obj.Position + point, obj.Radius, obj.Color, obj.Shape == RadarObjectShape.CircleFilled); + break; + } + default: + throw new NotImplementedException(); + } + } + } +} + +[UsedImplicitly] +public class RadarConsoleBoundUserInterface : ComputerBoundUserInterface +{ + public RadarConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {} +} diff --git a/Content.Server/Radar/RadarConsoleComponent.cs b/Content.Server/Radar/RadarConsoleComponent.cs new file mode 100644 index 0000000000..2fa6d38d5b --- /dev/null +++ b/Content.Server/Radar/RadarConsoleComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Radar; + +[RegisterComponent, ComponentProtoName("RadarConsole")] +public class RadarConsoleComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + [DataField("range")] + public float Range = 256f; +} diff --git a/Content.Server/Radar/RadarConsoleSystem.cs b/Content.Server/Radar/RadarConsoleSystem.cs new file mode 100644 index 0000000000..5603ef02db --- /dev/null +++ b/Content.Server/Radar/RadarConsoleSystem.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using Content.Server.Solar.Components; +using Content.Server.UserInterface; +using Content.Shared.Radar; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; + +namespace Content.Server.Radar; + +public class RadarConsoleSystem : EntitySystem +{ + [Dependency] private readonly IMapManager _mapManager = default!; + + private static float Frequency = 1.5f; + + private float _accumulator; + + public override void Update(float frameTime) + { + _accumulator += frameTime; + + if (_accumulator < Frequency) + return; + + _accumulator -= Frequency; + + foreach (var (component, xform) in EntityManager.EntityQuery()) + { + var s = component.Owner.GetUIOrNull(RadarConsoleUiKey.Key); + + if (s is null) + continue; + + var (radarPos, _, radarInvMatrix) = xform.GetWorldPositionRotationInvMatrix(); + + var mapId = xform.MapID; + var objects = new List(); + + _mapManager.FindGridsIntersectingEnumerator(mapId, new Box2(radarPos - component.Range, radarPos + component.Range), out var enumerator, true); + + while (enumerator.MoveNext(out var grid)) + { + var phy = Comp(grid.GridEntityId); + var transform = Transform(grid.GridEntityId); + + if (phy.Mass < 50) + continue; + + var rad = Math.Log2(phy.Mass); + var gridCenter = transform.WorldMatrix.Transform(phy.LocalCenter); + + var pos = radarInvMatrix.Transform(gridCenter); + pos.Y = -pos.Y; // Robust has an inverted Y, like BYOND. This undoes that. + + if (pos.Length > component.Range) + continue; + + objects.Add(new RadarObjectData {Color = Color.Aqua, Position = pos, Radius = (float)rad}); + } + + s.SetState(new RadarConsoleBoundInterfaceState(component.Range, objects.ToArray())); + } + } +} diff --git a/Content.Shared/Radar/RadarConsoleBoundInterfaceState.cs b/Content.Shared/Radar/RadarConsoleBoundInterfaceState.cs new file mode 100644 index 0000000000..1ce897a752 --- /dev/null +++ b/Content.Shared/Radar/RadarConsoleBoundInterfaceState.cs @@ -0,0 +1,40 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; + +namespace Content.Shared.Radar; + +[Serializable, NetSerializable] +public sealed class RadarConsoleBoundInterfaceState : BoundUserInterfaceState +{ + public float Range; + public RadarObjectData[] Objects; + + public RadarConsoleBoundInterfaceState(float range, RadarObjectData[] objects) + { + Range = range; + Objects = objects; + } +} + +[Serializable, NetSerializable] +public struct RadarObjectData +{ + public Color Color; + public RadarObjectShape Shape; + public Vector2 Position; + public float Radius; +} + +public enum RadarObjectShape : byte +{ + Circle, + CircleFilled, +} + +[Serializable, NetSerializable] +public enum RadarConsoleUiKey : byte +{ + Key +} diff --git a/Resources/Locale/en-US/misc-computers.ftl b/Resources/Locale/en-US/misc-computers.ftl new file mode 100644 index 0000000000..31d7cdc418 --- /dev/null +++ b/Resources/Locale/en-US/misc-computers.ftl @@ -0,0 +1 @@ +radar-window-title = Mass Scanner Console diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index be773721da..c10f53c7ca 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -67,6 +67,14 @@ - type: ComputerBoard prototype: ComputerComms +- type: entity + parent: BaseComputerCircuitboard + id: RadarConsoleCircuitboard + name: radar console computer board + components: + - type: ComputerBoard + prototype: ComputerRadar + - type: entity parent: BaseComputerCircuitboard id: SolarControlComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index e58a972998..776a82dc76 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -272,6 +272,32 @@ energy: 1.6 color: "#e6e227" +- type: entity + parent: ComputerBase + id: ComputerRadar + name: mass scanner computer + description: A computer for detecting nearby bodies, displaying them by position and mass. + components: + - type: Appearance + visuals: + - type: ComputerVisualizer + key: generic_key + screen: solar_screen + - type: RadarConsole + - type: ActivatableUI + key: enum.RadarConsoleUiKey.Key + - type: ActivatableUIRequiresPower + - type: UserInterface + interfaces: + - key: enum.RadarConsoleUiKey.Key + type: RadarConsoleBoundUserInterface + - type: Computer + board: RadarConsoleCircuitboard + - type: PointLight + radius: 1.5 + energy: 1.6 + color: "#e6e227" + - type: entity id: ComputerSupplyOrdering parent: ComputerBase