Power monitoring console overhaul (#20927)
* Prototyping whole station wire map * More prototyping * Added icons for the different power distributors and toggleable cable displays * Power cable layouts are now only sent to the client when the power monitor is open * UI prototyping * Power monitors can now see the sprites of distant entities, long entity names are truncated * Updated how network devices are added to the player's PVS * More feature prototypes * Added source / load symbols * Final prototype! Time to actually code it properly... * Start of code clean up * Continuing code clean up * Fixed UI appearance * Code clean up complete * Removed unnecessary changes * Updated how power values are calculated, added UI warnings for power sinks and power net checks * Updated how power values are calculated again, added support for portable generators * Removed unnecessary files * Map beacons start toggled off, console map now works outside the station, fixed substation icon * Made some of Sloth's requested changes. Power distributors don't blink anymore, unless selected * Moved a number of static variables in PowerMonitoringHelper to sensible places in the main files. Added a NavMapTrackableComponent so that you can specify how individual entities appear on the navmap * Updated the colors/positions of HV cables and SMESes to improve contrast * Fixed SMES color in map legend * Partially fixed auto-scrolling on device selection, made sublists alphabetical * Changed how auto-scroll is handled * Changed the font color of the console warning messages * Reduced the font size of beacon labels * Added the station name to the console * Organized references * Removed unwanted changes to RobustToolbox * Fix merge conflict * Fix merge conflict, maybe * Fix merge conflict * Updated outdated reference * Fixed portable_generator.yml * Implemented a number of requested changes, move bit masks to a shared component * Navigate listings via the navmap * First attempt at improving efficiency * Second attempt at optimization, entity grouping added for solar panels * Finished solar panel entity joining * Finished major revisions, code clean up needed * Finializing optimizations * Made requested changes * Bug fix, removed obsolete code * Bug fixes * Bug fixes * STarted revisions * Further revisions * More revision * Finalizing revisions. Need to make RT PR * Code tidying * More code tidying * Trying to avoid merge conflicts * Trying to avoid merge conflicts * Removed use of PVS * Improving efficiency * Addressed a bunch of outstanding issues * Clear old data on console refresh * UI adjustments * Made node comparison more robust. More devices can be combined into one entry * Added missing component 'dirty'
@@ -2,15 +2,15 @@
|
||||
xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'crew-monitoring-user-interface-title'}"
|
||||
SetSize="1200 700"
|
||||
MinSize="1200 700">
|
||||
SetSize="1210 700"
|
||||
MinSize="1210 700">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
|
||||
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20"/>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Vertical" Margin="0 0 10 0">
|
||||
<controls:StripeBack>
|
||||
<PanelContainer>
|
||||
<Label Name="StationName" Text="Unknown station" Align="Center" />
|
||||
<Label Name="StationName" Text="Unknown station" Align="Center" Margin="0 5 0 3"/>
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
|
||||
@@ -27,10 +27,12 @@ public partial class NavMapControl : MapGridControl
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
public EntityUid? Owner;
|
||||
public EntityUid? MapUid;
|
||||
|
||||
// Actions
|
||||
public event Action<NetEntity?>? TrackedEntitySelectedAction;
|
||||
public event Action<DrawingHandleScreen>? PostWallDrawingAction;
|
||||
|
||||
// Tracked data
|
||||
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
|
||||
@@ -358,6 +360,9 @@ public partial class NavMapControl : MapGridControl
|
||||
}
|
||||
}
|
||||
|
||||
if (PostWallDrawingAction != null)
|
||||
PostWallDrawingAction.Invoke(handle);
|
||||
|
||||
// Beacons
|
||||
if (_beacons.Pressed)
|
||||
{
|
||||
@@ -455,7 +460,7 @@ public partial class NavMapControl : MapGridControl
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNavMap()
|
||||
protected virtual void UpdateNavMap()
|
||||
{
|
||||
if (_navMap == null || _grid == null)
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using Content.Shared.Power;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
public sealed class PowerMonitoringConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private PowerMonitoringWindow? _menu;
|
||||
|
||||
public PowerMonitoringConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
_menu = new PowerMonitoringWindow(this, Owner);
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
var castState = (PowerMonitoringConsoleBoundInterfaceState) state;
|
||||
|
||||
if (castState == null)
|
||||
return;
|
||||
|
||||
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
|
||||
_menu?.ShowEntites
|
||||
(castState.TotalSources,
|
||||
castState.TotalBatteryUsage,
|
||||
castState.TotalLoads,
|
||||
castState.AllEntries,
|
||||
castState.FocusSources,
|
||||
castState.FocusLoads,
|
||||
xform?.Coordinates);
|
||||
}
|
||||
|
||||
public void SendPowerMonitoringConsoleMessage(NetEntity? netEntity, PowerMonitoringConsoleGroup group)
|
||||
{
|
||||
SendMessage(new PowerMonitoringConsoleMessage(netEntity, group));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
300
Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs
Normal file
@@ -0,0 +1,300 @@
|
||||
using Content.Client.Pinpointer.UI;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map.Components;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
// Cable indexing
|
||||
// 0: CableType.HighVoltage
|
||||
// 1: CableType.MediumVoltage
|
||||
// 2: CableType.Apc
|
||||
|
||||
private readonly Color[] _powerCableColors = { Color.OrangeRed, Color.Yellow, Color.LimeGreen };
|
||||
private readonly Vector2[] _powerCableOffsets = { new Vector2(-0.2f, -0.2f), Vector2.Zero, new Vector2(0.2f, 0.2f) };
|
||||
private Dictionary<Color, Color> _sRGBLookUp = new Dictionary<Color, Color>();
|
||||
|
||||
public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks;
|
||||
public List<PowerMonitoringConsoleLineGroup> HiddenLineGroups = new();
|
||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? PowerCableNetwork;
|
||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? FocusCableNetwork;
|
||||
|
||||
private MapGridComponent? _grid;
|
||||
|
||||
public PowerMonitoringConsoleNavMapControl() : base()
|
||||
{
|
||||
// Set colors
|
||||
TileColor = new Color(30, 57, 67);
|
||||
WallColor = new Color(102, 164, 217);
|
||||
|
||||
PostWallDrawingAction += DrawAllCableNetworks;
|
||||
}
|
||||
|
||||
protected override void UpdateNavMap()
|
||||
{
|
||||
base.UpdateNavMap();
|
||||
|
||||
if (Owner == null)
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetComponent<PowerMonitoringCableNetworksComponent>(Owner, out var cableNetworks))
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
||||
return;
|
||||
|
||||
PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks, _grid);
|
||||
FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks, _grid);
|
||||
}
|
||||
|
||||
public void DrawAllCableNetworks(DrawingHandleScreen handle)
|
||||
{
|
||||
// Draw full cable network
|
||||
if (PowerCableNetwork != null && PowerCableNetwork.Count > 0)
|
||||
{
|
||||
var modulator = (FocusCableNetwork != null && FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White;
|
||||
DrawCableNetwork(handle, PowerCableNetwork, modulator);
|
||||
}
|
||||
|
||||
// Draw focus network
|
||||
if (FocusCableNetwork != null && FocusCableNetwork.Count > 0)
|
||||
DrawCableNetwork(handle, FocusCableNetwork, Color.White);
|
||||
}
|
||||
|
||||
public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary<Vector2i, List<PowerMonitoringConsoleLine>> fullCableNetwork, Color modulator)
|
||||
{
|
||||
var offset = GetOffset();
|
||||
var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
|
||||
|
||||
if (WorldRange / WorldMaxRange > 0.5f)
|
||||
{
|
||||
var cableNetworks = new ValueList<Vector2>[3];
|
||||
|
||||
foreach ((var chunk, var chunkedLines) in fullCableNetwork)
|
||||
{
|
||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
||||
|
||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
||||
continue;
|
||||
|
||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
||||
continue;
|
||||
|
||||
foreach (var chunkedLine in chunkedLines)
|
||||
{
|
||||
if (HiddenLineGroups.Contains(chunkedLine.Group))
|
||||
continue;
|
||||
|
||||
var start = Scale(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
|
||||
var end = Scale(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
|
||||
|
||||
cableNetworks[(int) chunkedLine.Group].Add(start);
|
||||
cableNetworks[(int) chunkedLine.Group].Add(end);
|
||||
}
|
||||
}
|
||||
|
||||
for (int cableNetworkIdx = 0; cableNetworkIdx < cableNetworks.Length; cableNetworkIdx++)
|
||||
{
|
||||
var cableNetwork = cableNetworks[cableNetworkIdx];
|
||||
|
||||
if (cableNetwork.Count > 0)
|
||||
{
|
||||
var color = _powerCableColors[cableNetworkIdx] * modulator;
|
||||
|
||||
if (!_sRGBLookUp.TryGetValue(color, out var sRGB))
|
||||
{
|
||||
sRGB = Color.ToSrgb(color);
|
||||
_sRGBLookUp[color] = sRGB;
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, sRGB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var cableVertexUVs = new ValueList<Vector2>[3];
|
||||
|
||||
foreach ((var chunk, var chunkedLines) in fullCableNetwork)
|
||||
{
|
||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
||||
|
||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
||||
continue;
|
||||
|
||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
||||
continue;
|
||||
|
||||
foreach (var chunkedLine in chunkedLines)
|
||||
{
|
||||
if (HiddenLineGroups.Contains(chunkedLine.Group))
|
||||
continue;
|
||||
|
||||
var leftTop = Scale(new Vector2
|
||||
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
|
||||
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
|
||||
- new Vector2(offset.X, -offset.Y));
|
||||
|
||||
var rightTop = Scale(new Vector2
|
||||
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
|
||||
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
|
||||
- new Vector2(offset.X, -offset.Y));
|
||||
|
||||
var leftBottom = Scale(new Vector2
|
||||
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
|
||||
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
|
||||
- new Vector2(offset.X, -offset.Y));
|
||||
|
||||
var rightBottom = Scale(new Vector2
|
||||
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
|
||||
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
|
||||
- new Vector2(offset.X, -offset.Y));
|
||||
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftBottom);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftTop);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftTop);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightTop);
|
||||
}
|
||||
}
|
||||
|
||||
for (int cableNetworkIdx = 0; cableNetworkIdx < cableVertexUVs.Length; cableNetworkIdx++)
|
||||
{
|
||||
var cableVertexUV = cableVertexUVs[cableNetworkIdx];
|
||||
|
||||
if (cableVertexUV.Count > 0)
|
||||
{
|
||||
var color = _powerCableColors[cableNetworkIdx] * modulator;
|
||||
|
||||
if (!_sRGBLookUp.TryGetValue(color, out var sRGB))
|
||||
{
|
||||
sRGB = Color.ToSrgb(color);
|
||||
_sRGBLookUp[color] = sRGB;
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, sRGB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? GetDecodedPowerCableChunks(Dictionary<Vector2i, PowerCableChunk>? chunks, MapGridComponent? grid)
|
||||
{
|
||||
if (chunks == null || grid == null)
|
||||
return null;
|
||||
|
||||
var decodedOutput = new Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>();
|
||||
|
||||
foreach ((var chunkOrigin, var chunk) in chunks)
|
||||
{
|
||||
var list = new List<PowerMonitoringConsoleLine>();
|
||||
|
||||
for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++)
|
||||
{
|
||||
var chunkMask = chunk.PowerCableData[cableIdx];
|
||||
|
||||
Vector2 offset = _powerCableOffsets[cableIdx];
|
||||
|
||||
for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++)
|
||||
{
|
||||
var value = (int) Math.Pow(2, chunkIdx);
|
||||
var mask = chunkMask & value;
|
||||
|
||||
if (mask == 0x0)
|
||||
continue;
|
||||
|
||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
|
||||
var position = new Vector2(tile.X, -tile.Y);
|
||||
|
||||
PowerCableChunk neighborChunk;
|
||||
bool neighbor;
|
||||
|
||||
// Note: we only check the north and east neighbors
|
||||
|
||||
// East
|
||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
||||
{
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
||||
(neighborChunk.PowerCableData[cableIdx] & SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
||||
neighbor = (chunkMask & flag) != 0x0;
|
||||
}
|
||||
|
||||
if (neighbor)
|
||||
{
|
||||
// Add points
|
||||
var line = new PowerMonitoringConsoleLine
|
||||
(position + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
||||
position + new Vector2(1f, 0f) + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
||||
(PowerMonitoringConsoleLineGroup) cableIdx);
|
||||
|
||||
list.Add(line);
|
||||
}
|
||||
|
||||
// North
|
||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
||||
{
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
||||
(neighborChunk.PowerCableData[cableIdx] & SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
||||
neighbor = (chunkMask & flag) != 0x0;
|
||||
}
|
||||
|
||||
if (neighbor)
|
||||
{
|
||||
// Add points
|
||||
var line = new PowerMonitoringConsoleLine
|
||||
(position + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
||||
position + new Vector2(0f, -1f) + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
||||
(PowerMonitoringConsoleLineGroup) cableIdx);
|
||||
|
||||
list.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (list.Count > 0)
|
||||
decodedOutput.Add(chunkOrigin, list);
|
||||
}
|
||||
|
||||
return decodedOutput;
|
||||
}
|
||||
}
|
||||
|
||||
public struct PowerMonitoringConsoleLine
|
||||
{
|
||||
public readonly Vector2 Origin;
|
||||
public readonly Vector2 Terminus;
|
||||
public readonly PowerMonitoringConsoleLineGroup Group;
|
||||
|
||||
public PowerMonitoringConsoleLine(Vector2 origin, Vector2 terminus, PowerMonitoringConsoleLineGroup group)
|
||||
{
|
||||
Origin = origin;
|
||||
Terminus = terminus;
|
||||
Group = group;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PowerMonitoringConsoleLineGroup : byte
|
||||
{
|
||||
HighVoltage,
|
||||
MediumVoltage,
|
||||
Apc,
|
||||
}
|
||||
@@ -1,20 +1,117 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'power-monitoring-window-title'}">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Power"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'power-monitoring-window-title'}"
|
||||
SetSize="1120 750"
|
||||
MinSize="1120 750">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<!-- Main display -->
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
|
||||
<!-- Nav map -->
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
|
||||
<ui:PowerMonitoringConsoleNavMapControl Name="NavMap" Margin="5 5" VerticalExpand="True" HorizontalExpand="True">
|
||||
|
||||
<!-- System warning -->
|
||||
<PanelContainer Name="SystemWarningPanel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 48 0 0"
|
||||
Visible="False">
|
||||
<RichTextLabel Name="SystemWarningLabel" Margin="12 8 12 8"/>
|
||||
</PanelContainer>
|
||||
|
||||
|
||||
</ui:PowerMonitoringConsoleNavMapControl>
|
||||
|
||||
<!-- Nav map legend -->
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 10 0 10">
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
|
||||
Modulate="#800080"
|
||||
SetSize="16 16"
|
||||
Margin="20 0 5 0"/>
|
||||
<Label Text="{Loc 'power-monitoring-window-label-sources'}"/>
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_hexagon.png"
|
||||
SetSize="16 16"
|
||||
Modulate="#ff4500"
|
||||
Margin="20 0 5 0"/>
|
||||
<Label Text="{Loc 'power-monitoring-window-label-smes'}"/>
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_square.png"
|
||||
SetSize="16 16"
|
||||
Modulate="#ffff00"
|
||||
Margin="20 0 5 0"/>
|
||||
<Label Text="{Loc 'power-monitoring-window-label-substation'}"/>
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
|
||||
SetSize="16 16"
|
||||
Modulate="#32cd32"
|
||||
Margin="20 0 5 0"/>
|
||||
<Label Text="{Loc 'power-monitoring-window-label-apc'}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Power status -->
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" SetWidth="440" Margin="0 0 10 10">
|
||||
|
||||
<!-- Station name -->
|
||||
<controls:StripeBack>
|
||||
<PanelContainer>
|
||||
<RichTextLabel Name="StationName" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 5 0 3"/>
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<!-- Power overview -->
|
||||
<GridContainer Columns="2">
|
||||
<!-- Grid is used here to align things. -->
|
||||
<Label Text="{Loc 'power-monitoring-window-total-sources'}"/><Label Name="TotalSourcesNum" Text="?"/>
|
||||
<Label Text="{Loc 'power-monitoring-window-total-loads'}"/><Label Name="TotalLoadsNum" Text="?"/>
|
||||
<Label StyleClasses="StatusFieldTitle" Text="{Loc 'power-monitoring-window-total-sources'}"/>
|
||||
<Label Name="TotalSources" Text="?" Margin="10 0 0 0"/>
|
||||
<Label StyleClasses="StatusFieldTitle" Text="{Loc 'power-monitoring-window-total-battery-usage'}"/>
|
||||
<Label Name="TotalBatteryUsage" Text="?" Margin="10 0 0 0"/>
|
||||
<Label StyleClasses="StatusFieldTitle" Text="{Loc 'power-monitoring-window-total-loads'}"/>
|
||||
<Label Name="TotalLoads" Text="?" Margin="10 0 0 0"/>
|
||||
</GridContainer>
|
||||
<TabContainer Name="MasterTabContainer" VerticalExpand="True">
|
||||
<ItemList Name="SourcesList" VerticalExpand="True">
|
||||
</ItemList>
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<CheckBox Margin="8 8" Name="ShowInactiveConsumersCheckBox" Text="{Loc 'power-monitoring-window-show-inactive-consumers'}" />
|
||||
<ItemList Name="LoadsList" VerticalExpand="True">
|
||||
</ItemList>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Loads / Sources (entries added by C# code) -->
|
||||
<TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True" Margin="0 10 0 0">
|
||||
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
|
||||
<BoxContainer Name="SourcesList" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 10 0"/>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
|
||||
<BoxContainer Name="SMESList" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 10 0"/>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
|
||||
<BoxContainer Name="SubstationList" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 10 0"/>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
|
||||
<BoxContainer Name="ApcList" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 10 0"/>
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
|
||||
<!-- Cable network toggles -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
|
||||
<Label Text="{Loc 'power-monitoring-window-show-cable-networks'}" Margin="0 0 0 5"/>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<CheckBox Name="ShowHVCable" Text="{Loc 'power-monitoring-window-show-hv-cable'}" Pressed="True" Margin="0 0 0 0"/>
|
||||
<CheckBox Name="ShowMVCable" Text="{Loc 'power-monitoring-window-show-mv-cable'}" Pressed="True" Margin="10 0 0 0"/>
|
||||
<CheckBox Name="ShowLVCable" Text="{Loc 'power-monitoring-window-show-lv-cable'}" Pressed="True" Margin="10 0 0 0"/>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'power-monitoring-window-flavor-left'}" StyleClasses="WindowFooterText" />
|
||||
<Label Text="{Loc 'power-monitoring-window-flavor-right'}" StyleClasses="WindowFooterText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
|
||||
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
490
Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
Normal file
@@ -0,0 +1,490 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
public sealed partial class PowerMonitoringWindow
|
||||
{
|
||||
private SpriteSpecifier.Texture _sourceIcon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/PowerMonitoring/source_arrow.png"));
|
||||
private SpriteSpecifier.Texture _loadIconPath = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/PowerMonitoring/load_arrow.png"));
|
||||
|
||||
private bool _autoScrollActive = false;
|
||||
private bool _autoScrollAwaitsUpdate = false;
|
||||
|
||||
private void UpdateWindowConsoleEntry
|
||||
(BoxContainer masterContainer,
|
||||
int index,
|
||||
PowerMonitoringConsoleEntry entry,
|
||||
PowerMonitoringConsoleEntry[] focusSources,
|
||||
PowerMonitoringConsoleEntry[] focusLoads)
|
||||
{
|
||||
UpdateWindowConsoleEntry(masterContainer, index, entry);
|
||||
|
||||
var windowEntry = masterContainer.GetChild(index) as PowerMonitoringWindowEntry;
|
||||
|
||||
// If we exit here, something was added to the container that shouldn't have been added
|
||||
if (windowEntry == null)
|
||||
return;
|
||||
|
||||
// Update sources and loads
|
||||
UpdateEntrySourcesOrLoads(masterContainer, windowEntry.SourcesContainer, focusSources, _sourceIcon);
|
||||
UpdateEntrySourcesOrLoads(masterContainer, windowEntry.LoadsContainer, focusLoads, _loadIconPath);
|
||||
|
||||
windowEntry.MainContainer.Visible = true;
|
||||
}
|
||||
|
||||
private void UpdateWindowConsoleEntry(BoxContainer masterContainer, int index, PowerMonitoringConsoleEntry entry)
|
||||
{
|
||||
PowerMonitoringWindowEntry? windowEntry;
|
||||
|
||||
// Add missing children
|
||||
if (index >= masterContainer.ChildCount)
|
||||
{
|
||||
// Add basic entry
|
||||
windowEntry = new PowerMonitoringWindowEntry(entry);
|
||||
masterContainer.AddChild(windowEntry);
|
||||
|
||||
// Selection action
|
||||
windowEntry.Button.OnButtonUp += args =>
|
||||
{
|
||||
windowEntry.SourcesContainer.DisposeAllChildren();
|
||||
windowEntry.LoadsContainer.DisposeAllChildren();
|
||||
ButtonAction(windowEntry, masterContainer);
|
||||
};
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
windowEntry = masterContainer.GetChild(index) as PowerMonitoringWindowEntry;
|
||||
}
|
||||
|
||||
// If we exit here, something was added to the container that shouldn't have been added
|
||||
if (windowEntry == null)
|
||||
return;
|
||||
|
||||
windowEntry.NetEntity = entry.NetEntity;
|
||||
windowEntry.Entry = entry;
|
||||
windowEntry.MainContainer.Visible = false;
|
||||
|
||||
UpdateWindowEntryButton(entry.NetEntity, windowEntry.Button, entry);
|
||||
}
|
||||
|
||||
public void UpdateWindowEntryButton(NetEntity netEntity, PowerMonitoringButton button, PowerMonitoringConsoleEntry entry)
|
||||
{
|
||||
if (!netEntity.IsValid())
|
||||
return;
|
||||
|
||||
if (entry.MetaData == null)
|
||||
return;
|
||||
|
||||
// Update button style
|
||||
if (netEntity == _focusEntity)
|
||||
button.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
|
||||
else
|
||||
button.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
|
||||
// Update sprite
|
||||
if (entry.MetaData.Value.SpritePath != string.Empty && entry.MetaData.Value.SpriteState != string.Empty)
|
||||
button.TextureRect.Texture = _spriteSystem.Frame0(new SpriteSpecifier.Rsi(new ResPath(entry.MetaData.Value.SpritePath), entry.MetaData.Value.SpriteState));
|
||||
|
||||
// Update name
|
||||
var name = Loc.GetString(entry.MetaData.Value.EntityName);
|
||||
button.NameLocalized.Text = name;
|
||||
|
||||
// Update tool tip
|
||||
button.ToolTip = Loc.GetString(name);
|
||||
|
||||
// Update power value
|
||||
button.PowerValue.Text = Loc.GetString("power-monitoring-window-value", ("value", entry.PowerValue));
|
||||
}
|
||||
|
||||
private void UpdateEntrySourcesOrLoads(BoxContainer masterContainer, BoxContainer currentContainer, PowerMonitoringConsoleEntry[]? entries, SpriteSpecifier.Texture icon)
|
||||
{
|
||||
if (currentContainer == null)
|
||||
return;
|
||||
|
||||
if (entries == null || entries.Length == 0)
|
||||
{
|
||||
currentContainer.RemoveAllChildren();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove excess children
|
||||
while (currentContainer.ChildCount > entries.Length)
|
||||
{
|
||||
currentContainer.RemoveChild(currentContainer.GetChild(currentContainer.ChildCount - 1));
|
||||
}
|
||||
|
||||
// Add missing children
|
||||
while (currentContainer.ChildCount < entries.Length)
|
||||
{
|
||||
var entry = entries[currentContainer.ChildCount];
|
||||
var subEntry = new PowerMonitoringWindowSubEntry(entry);
|
||||
currentContainer.AddChild(subEntry);
|
||||
|
||||
// Selection action
|
||||
subEntry.Button.OnButtonUp += args => { ButtonAction(subEntry, masterContainer); };
|
||||
}
|
||||
|
||||
if (!_entManager.TryGetComponent<PowerMonitoringConsoleComponent>(_owner, out var console))
|
||||
return;
|
||||
|
||||
// Update all children
|
||||
foreach (var child in currentContainer.Children)
|
||||
{
|
||||
if (child is not PowerMonitoringWindowSubEntry)
|
||||
continue;
|
||||
|
||||
var castChild = (PowerMonitoringWindowSubEntry) child;
|
||||
|
||||
if (castChild == null)
|
||||
continue;
|
||||
|
||||
if (castChild.Icon != null)
|
||||
castChild.Icon.Texture = _spriteSystem.Frame0(icon);
|
||||
|
||||
var entry = entries[child.GetPositionInParent()];
|
||||
|
||||
castChild.NetEntity = entry.NetEntity;
|
||||
castChild.Entry = entry;
|
||||
|
||||
UpdateWindowEntryButton(entry.NetEntity, castChild.Button, entries.ElementAt(child.GetPositionInParent()));
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonAction(PowerMonitoringWindowBaseEntry entry, BoxContainer masterContainer)
|
||||
{
|
||||
// Toggle off button?
|
||||
if (entry.NetEntity == _focusEntity)
|
||||
{
|
||||
entry.Button.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
_focusEntity = null;
|
||||
|
||||
// Request an update from the power monitoring system
|
||||
SendPowerMonitoringConsoleMessageAction?.Invoke(null, entry.Entry.Group);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, toggle on
|
||||
entry.Button.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
|
||||
ActivateAutoScrollToFocus();
|
||||
|
||||
// Toggle off the old button (if applicable)
|
||||
if (_focusEntity != null)
|
||||
{
|
||||
foreach (PowerMonitoringWindowEntry sibling in masterContainer.Children)
|
||||
{
|
||||
if (sibling.NetEntity == _focusEntity)
|
||||
{
|
||||
sibling.Button.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Center the nav map on selected entity
|
||||
_focusEntity = entry.NetEntity;
|
||||
|
||||
if (!NavMap.TrackedEntities.TryGetValue(entry.NetEntity, out var blip))
|
||||
return;
|
||||
|
||||
NavMap.CenterToCoordinates(blip.Coordinates);
|
||||
|
||||
// Switch tabs
|
||||
SwitchTabsBasedOnPowerMonitoringConsoleGroup(entry.Entry.Group);
|
||||
|
||||
// Send an update from the power monitoring system
|
||||
SendPowerMonitoringConsoleMessageAction?.Invoke(_focusEntity, entry.Entry.Group);
|
||||
}
|
||||
|
||||
private void ActivateAutoScrollToFocus()
|
||||
{
|
||||
_autoScrollActive = false;
|
||||
_autoScrollAwaitsUpdate = true;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = null;
|
||||
|
||||
var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
|
||||
if (scroll == null)
|
||||
return false;
|
||||
|
||||
var container = scroll.Children.ElementAt(0) as BoxContainer;
|
||||
if (container == null || container.Children.Count() == 0)
|
||||
return false;
|
||||
|
||||
// Exit if the heights of the children haven't been initialized yet
|
||||
if (!container.Children.Any(x => x.Height > 0))
|
||||
return false;
|
||||
|
||||
nextScrollPosition = 0;
|
||||
|
||||
foreach (var control in container.Children)
|
||||
{
|
||||
if (control == null || control is not PowerMonitoringWindowEntry)
|
||||
continue;
|
||||
|
||||
if (((PowerMonitoringWindowEntry) control).NetEntity == _focusEntity)
|
||||
return true;
|
||||
|
||||
nextScrollPosition += control.Height;
|
||||
}
|
||||
|
||||
// Failed to find control
|
||||
nextScrollPosition = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var child in scroll.Children)
|
||||
{
|
||||
if (child is not VScrollBar)
|
||||
continue;
|
||||
|
||||
var castChild = child as VScrollBar;
|
||||
|
||||
if (castChild != null)
|
||||
{
|
||||
vScrollBar = castChild;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AutoScrollToFocus()
|
||||
{
|
||||
if (!_autoScrollActive)
|
||||
return;
|
||||
|
||||
var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
|
||||
if (scroll == null)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
return;
|
||||
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
_autoScrollActive = false;
|
||||
}
|
||||
|
||||
private void UpdateWarningLabel(PowerMonitoringFlags flags)
|
||||
{
|
||||
if (flags == PowerMonitoringFlags.None)
|
||||
{
|
||||
SystemWarningPanel.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = new FormattedMessage();
|
||||
|
||||
if ((flags & PowerMonitoringFlags.RoguePowerConsumer) != 0)
|
||||
{
|
||||
SystemWarningPanel.PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.Red,
|
||||
BorderColor = Color.DarkRed,
|
||||
BorderThickness = new Thickness(2),
|
||||
};
|
||||
|
||||
msg.AddMarkup(Loc.GetString("power-monitoring-window-rogue-power-consumer"));
|
||||
SystemWarningPanel.Visible = true;
|
||||
}
|
||||
|
||||
else if ((flags & PowerMonitoringFlags.PowerNetAbnormalities) != 0)
|
||||
{
|
||||
SystemWarningPanel.PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.Orange,
|
||||
BorderColor = Color.DarkOrange,
|
||||
BorderThickness = new Thickness(2),
|
||||
};
|
||||
|
||||
msg.AddMarkup(Loc.GetString("power-monitoring-window-power-net-abnormalities"));
|
||||
SystemWarningPanel.Visible = true;
|
||||
}
|
||||
|
||||
SystemWarningLabel.SetMessage(msg);
|
||||
}
|
||||
|
||||
private void SwitchTabsBasedOnPowerMonitoringConsoleGroup(PowerMonitoringConsoleGroup group)
|
||||
{
|
||||
switch (group)
|
||||
{
|
||||
case PowerMonitoringConsoleGroup.Generator:
|
||||
MasterTabContainer.CurrentTab = 0; break;
|
||||
case PowerMonitoringConsoleGroup.SMES:
|
||||
MasterTabContainer.CurrentTab = 1; break;
|
||||
case PowerMonitoringConsoleGroup.Substation:
|
||||
MasterTabContainer.CurrentTab = 2; break;
|
||||
case PowerMonitoringConsoleGroup.APC:
|
||||
MasterTabContainer.CurrentTab = 3; break;
|
||||
}
|
||||
}
|
||||
|
||||
private PowerMonitoringConsoleGroup GetCurrentPowerMonitoringConsoleGroup()
|
||||
{
|
||||
return (PowerMonitoringConsoleGroup) MasterTabContainer.CurrentTab;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PowerMonitoringWindowEntry : PowerMonitoringWindowBaseEntry
|
||||
{
|
||||
public BoxContainer MainContainer;
|
||||
public BoxContainer SourcesContainer;
|
||||
public BoxContainer LoadsContainer;
|
||||
|
||||
public PowerMonitoringWindowEntry(PowerMonitoringConsoleEntry entry) : base(entry)
|
||||
{
|
||||
Entry = entry;
|
||||
|
||||
// Alignment
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
|
||||
// Update selection button
|
||||
Button.StyleClasses.Add("OpenLeft");
|
||||
AddChild(Button);
|
||||
|
||||
// Grid container to hold sub containers
|
||||
MainContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(8, 0, 0, 0),
|
||||
Visible = false,
|
||||
};
|
||||
|
||||
AddChild(MainContainer);
|
||||
|
||||
// Grid container to hold the list of sources when selected
|
||||
SourcesContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
MainContainer.AddChild(SourcesContainer);
|
||||
|
||||
// Grid container to hold the list of loads when selected
|
||||
LoadsContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
MainContainer.AddChild(LoadsContainer);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PowerMonitoringWindowSubEntry : PowerMonitoringWindowBaseEntry
|
||||
{
|
||||
public TextureRect? Icon;
|
||||
|
||||
public PowerMonitoringWindowSubEntry(PowerMonitoringConsoleEntry entry) : base(entry)
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal;
|
||||
HorizontalExpand = true;
|
||||
|
||||
// Source/load icon
|
||||
Icon = new TextureRect()
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 2, 0),
|
||||
};
|
||||
|
||||
AddChild(Icon);
|
||||
|
||||
// Selection button
|
||||
Button.StyleClasses.Add("OpenBoth");
|
||||
AddChild(Button);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class PowerMonitoringWindowBaseEntry : BoxContainer
|
||||
{
|
||||
public NetEntity NetEntity;
|
||||
public PowerMonitoringConsoleEntry Entry;
|
||||
public PowerMonitoringButton Button;
|
||||
|
||||
public PowerMonitoringWindowBaseEntry(PowerMonitoringConsoleEntry entry)
|
||||
{
|
||||
Entry = entry;
|
||||
|
||||
// Add selection button (properties set by derivative classes)
|
||||
Button = new PowerMonitoringButton();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PowerMonitoringButton : Button
|
||||
{
|
||||
public BoxContainer MainContainer;
|
||||
public TextureRect TextureRect;
|
||||
public Label NameLocalized;
|
||||
public Label PowerValue;
|
||||
|
||||
public PowerMonitoringButton()
|
||||
{
|
||||
HorizontalExpand = true;
|
||||
VerticalExpand = true;
|
||||
Margin = new Thickness(0f, 1f, 0f, 1f);
|
||||
|
||||
MainContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
SetHeight = 32f,
|
||||
};
|
||||
|
||||
AddChild(MainContainer);
|
||||
|
||||
TextureRect = new TextureRect()
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SetSize = new Vector2(32f, 32f),
|
||||
Margin = new Thickness(0f, 0f, 5f, 0f),
|
||||
};
|
||||
|
||||
MainContainer.AddChild(TextureRect);
|
||||
|
||||
NameLocalized = new Label()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
ClipText = true,
|
||||
};
|
||||
|
||||
MainContainer.AddChild(NameLocalized);
|
||||
|
||||
PowerValue = new Label()
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
SetWidth = 72f,
|
||||
Margin = new Thickness(10, 0, 0, 0),
|
||||
ClipText = true,
|
||||
};
|
||||
|
||||
MainContainer.AddChild(PowerValue);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,317 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Computer;
|
||||
using Content.Client.Pinpointer.UI;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Power;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PowerMonitoringWindow : DefaultWindow, IComputerWindow<PowerMonitoringConsoleBoundInterfaceState>
|
||||
public sealed partial class PowerMonitoringWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private readonly SpriteSystem _spriteSystem = default!;
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private readonly IGameTiming _gameTiming;
|
||||
|
||||
public PowerMonitoringWindow()
|
||||
private const float BlinkFrequency = 1f;
|
||||
|
||||
private EntityUid? _owner;
|
||||
private NetEntity? _focusEntity;
|
||||
|
||||
public event Action<NetEntity?, PowerMonitoringConsoleGroup>? SendPowerMonitoringConsoleMessageAction;
|
||||
|
||||
private Dictionary<PowerMonitoringConsoleGroup, (SpriteSpecifier.Texture, Color)> _groupBlips = new()
|
||||
{
|
||||
{ PowerMonitoringConsoleGroup.Generator, (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), Color.Purple) },
|
||||
{ PowerMonitoringConsoleGroup.SMES, (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_hexagon.png")), Color.OrangeRed) },
|
||||
{ PowerMonitoringConsoleGroup.Substation, (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), Color.Yellow) },
|
||||
{ PowerMonitoringConsoleGroup.APC, (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), Color.LimeGreen) },
|
||||
};
|
||||
|
||||
public PowerMonitoringWindow(PowerMonitoringConsoleBoundUserInterface userInterface, EntityUid? owner)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
SetSize = MinSize = new Vector2(300, 450);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_spriteSystem = IoCManager.Resolve<IEntityManager>().System<SpriteSystem>();
|
||||
MasterTabContainer.SetTabTitle(0, Loc.GetString("power-monitoring-window-tab-sources"));
|
||||
MasterTabContainer.SetTabTitle(1, Loc.GetString("power-monitoring-window-tab-loads"));
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
_owner = owner;
|
||||
|
||||
// Pass owner to nav map
|
||||
NavMap.Owner = _owner;
|
||||
|
||||
// Set nav map grid uid
|
||||
var stationName = Loc.GetString("power-monitoring-window-unknown-location");
|
||||
|
||||
if (_entManager.TryGetComponent<TransformComponent>(owner, out var xform))
|
||||
{
|
||||
NavMap.MapUid = xform.GridUid;
|
||||
|
||||
// Assign station name
|
||||
if (_entManager.TryGetComponent<MetaDataComponent>(xform.GridUid, out var stationMetaData))
|
||||
stationName = stationMetaData.EntityName;
|
||||
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddMarkup(Loc.GetString("power-monitoring-window-station-name", ("stationName", stationName)));
|
||||
|
||||
StationName.SetMessage(msg);
|
||||
}
|
||||
|
||||
public void UpdateState(PowerMonitoringConsoleBoundInterfaceState scc)
|
||||
else
|
||||
{
|
||||
UpdateList(TotalSourcesNum, scc.TotalSources, SourcesList, scc.Sources);
|
||||
var loads = scc.Loads;
|
||||
if (!ShowInactiveConsumersCheckBox.Pressed)
|
||||
{
|
||||
// Not showing inactive consumers, so hiding them.
|
||||
// This means filtering out loads that are not either:
|
||||
// + Batteries (always important)
|
||||
// + Meaningful (size above 0)
|
||||
loads = loads.Where(a => a.IsBattery || a.Size > 0.0f).ToArray();
|
||||
}
|
||||
UpdateList(TotalLoadsNum, scc.TotalLoads, LoadsList, loads);
|
||||
StationName.SetMessage(stationName);
|
||||
NavMap.Visible = false;
|
||||
}
|
||||
|
||||
public void UpdateList(Label number, double numberVal, ItemList list, PowerMonitoringConsoleEntry[] listVal)
|
||||
{
|
||||
number.Text = Loc.GetString("power-monitoring-window-value", ("value", numberVal));
|
||||
// This magic is important to prevent scrolling issues.
|
||||
while (list.Count > listVal.Length)
|
||||
{
|
||||
list.RemoveAt(list.Count - 1);
|
||||
// Set trackable entity selected action
|
||||
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
|
||||
|
||||
// Update nav map
|
||||
NavMap.ForceNavMapUpdate();
|
||||
|
||||
// Set UI tab titles
|
||||
MasterTabContainer.SetTabTitle(0, Loc.GetString("power-monitoring-window-label-sources"));
|
||||
MasterTabContainer.SetTabTitle(1, Loc.GetString("power-monitoring-window-label-smes"));
|
||||
MasterTabContainer.SetTabTitle(2, Loc.GetString("power-monitoring-window-label-substation"));
|
||||
MasterTabContainer.SetTabTitle(3, Loc.GetString("power-monitoring-window-label-apc"));
|
||||
|
||||
// Track when the MasterTabContainer changes its tab
|
||||
MasterTabContainer.OnTabChanged += OnTabChanged;
|
||||
|
||||
// Set UI toggles
|
||||
ShowHVCable.OnToggled += _ => OnShowCableToggled(PowerMonitoringConsoleLineGroup.HighVoltage);
|
||||
ShowMVCable.OnToggled += _ => OnShowCableToggled(PowerMonitoringConsoleLineGroup.MediumVoltage);
|
||||
ShowLVCable.OnToggled += _ => OnShowCableToggled(PowerMonitoringConsoleLineGroup.Apc);
|
||||
|
||||
// Set power monitoring message action
|
||||
SendPowerMonitoringConsoleMessageAction += userInterface.SendPowerMonitoringConsoleMessage;
|
||||
}
|
||||
while (list.Count < listVal.Length)
|
||||
|
||||
private void OnTabChanged(int tab)
|
||||
{
|
||||
list.AddItem("YOU SHOULD NEVER SEE THIS (REALLY!)", null, false);
|
||||
SendPowerMonitoringConsoleMessageAction?.Invoke(_focusEntity, (PowerMonitoringConsoleGroup) tab);
|
||||
}
|
||||
// Now overwrite the items properly...
|
||||
for (var i = 0; i < listVal.Length; i++)
|
||||
|
||||
private void OnShowCableToggled(PowerMonitoringConsoleLineGroup lineGroup)
|
||||
{
|
||||
var ent = listVal[i];
|
||||
_prototypeManager.TryIndex(ent.IconEntityPrototypeId, out EntityPrototype? entityPrototype);
|
||||
IRsiStateLike? iconState = null;
|
||||
if (entityPrototype != null)
|
||||
iconState = _spriteSystem.GetPrototypeIcon(entityPrototype);
|
||||
var icon = iconState?.GetFrame(RsiDirection.South, 0);
|
||||
var item = list[i];
|
||||
item.Text = $"{ent.NameLocalized} {Loc.GetString("power-monitoring-window-value", ("value", ent.Size))}";
|
||||
item.Icon = icon;
|
||||
if (!NavMap.HiddenLineGroups.Remove(lineGroup))
|
||||
NavMap.HiddenLineGroups.Add(lineGroup);
|
||||
}
|
||||
|
||||
public void ShowEntites
|
||||
(double totalSources,
|
||||
double totalBatteryUsage,
|
||||
double totalLoads,
|
||||
PowerMonitoringConsoleEntry[] allEntries,
|
||||
PowerMonitoringConsoleEntry[] focusSources,
|
||||
PowerMonitoringConsoleEntry[] focusLoads,
|
||||
EntityCoordinates? monitorCoords)
|
||||
{
|
||||
if (_owner == null)
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetComponent<PowerMonitoringConsoleComponent>(_owner.Value, out var console))
|
||||
return;
|
||||
|
||||
// Update power status text
|
||||
TotalSources.Text = Loc.GetString("power-monitoring-window-value", ("value", totalSources));
|
||||
TotalBatteryUsage.Text = Loc.GetString("power-monitoring-window-value", ("value", totalBatteryUsage));
|
||||
TotalLoads.Text = Loc.GetString("power-monitoring-window-value", ("value", totalLoads));
|
||||
|
||||
// 10+% of station power is being drawn from batteries
|
||||
TotalBatteryUsage.FontColorOverride = (totalSources * 0.1111f) < totalBatteryUsage ? new Color(180, 0, 0) : Color.White;
|
||||
|
||||
// Station generator and battery output is less than the current demand
|
||||
TotalLoads.FontColorOverride = (totalSources + totalBatteryUsage) < totalLoads &&
|
||||
!MathHelper.CloseToPercent(totalSources + totalBatteryUsage, totalLoads, 0.1f) ? new Color(180, 0, 0) : Color.White;
|
||||
|
||||
// Update system warnings
|
||||
UpdateWarningLabel(console.Flags);
|
||||
|
||||
// Reset nav map values
|
||||
NavMap.TrackedCoordinates.Clear();
|
||||
NavMap.TrackedEntities.Clear();
|
||||
|
||||
// Draw entities on the nav map
|
||||
var entitiesOfInterest = new List<NetEntity>();
|
||||
|
||||
if (_focusEntity != null)
|
||||
{
|
||||
entitiesOfInterest.Add(_focusEntity.Value);
|
||||
|
||||
foreach (var entry in focusSources)
|
||||
entitiesOfInterest.Add(entry.NetEntity);
|
||||
|
||||
foreach (var entry in focusLoads)
|
||||
entitiesOfInterest.Add(entry.NetEntity);
|
||||
}
|
||||
|
||||
focusSources.Concat(focusLoads);
|
||||
|
||||
foreach ((var netEntity, var metaData) in console.PowerMonitoringDeviceMetaData)
|
||||
{
|
||||
if (NavMap.Visible)
|
||||
AddTrackedEntityToNavMap(netEntity, metaData, entitiesOfInterest);
|
||||
}
|
||||
|
||||
// Show monitor location
|
||||
var mon = _entManager.GetNetEntity(_owner);
|
||||
|
||||
if (monitorCoords != null && mon != null)
|
||||
{
|
||||
var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
|
||||
var blip = new NavMapBlip(monitorCoords.Value, texture, Color.Cyan, true, false);
|
||||
NavMap.TrackedEntities[mon.Value] = blip;
|
||||
}
|
||||
|
||||
// Update nav map
|
||||
NavMap.ForceNavMapUpdate();
|
||||
|
||||
// If the entry group doesn't match the current tab, the data is out dated, do not use it
|
||||
if (allEntries.Length > 0 && allEntries[0].Group != GetCurrentPowerMonitoringConsoleGroup())
|
||||
return;
|
||||
|
||||
// Assign meta data to the console entries and sort them
|
||||
allEntries = GetUpdatedPowerMonitoringConsoleEntries(allEntries, console);
|
||||
focusSources = GetUpdatedPowerMonitoringConsoleEntries(focusSources, console);
|
||||
focusLoads = GetUpdatedPowerMonitoringConsoleEntries(focusLoads, console);
|
||||
|
||||
// Get current console entry container
|
||||
BoxContainer currentContainer = SourcesList;
|
||||
switch (GetCurrentPowerMonitoringConsoleGroup())
|
||||
{
|
||||
case PowerMonitoringConsoleGroup.SMES:
|
||||
currentContainer = SMESList; break;
|
||||
case PowerMonitoringConsoleGroup.Substation:
|
||||
currentContainer = SubstationList; break;
|
||||
case PowerMonitoringConsoleGroup.APC:
|
||||
currentContainer = ApcList; break;
|
||||
}
|
||||
|
||||
// Clear excess children from the container
|
||||
while (currentContainer.ChildCount > allEntries.Length)
|
||||
currentContainer.RemoveChild(currentContainer.GetChild(currentContainer.ChildCount - 1));
|
||||
|
||||
// Update the remaining children
|
||||
for (var index = 0; index < allEntries.Length; index++)
|
||||
{
|
||||
var entry = allEntries[index];
|
||||
|
||||
if (entry.NetEntity == _focusEntity)
|
||||
UpdateWindowConsoleEntry(currentContainer, index, entry, focusSources, focusLoads);
|
||||
|
||||
else
|
||||
UpdateWindowConsoleEntry(currentContainer, index, entry);
|
||||
}
|
||||
|
||||
// Auto-scroll renable
|
||||
if (_autoScrollAwaitsUpdate)
|
||||
{
|
||||
_autoScrollActive = true;
|
||||
_autoScrollAwaitsUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTrackedEntityToNavMap(NetEntity netEntity, PowerMonitoringDeviceMetaData metaData, List<NetEntity> entitiesOfInterest)
|
||||
{
|
||||
if (!_groupBlips.TryGetValue(metaData.Group, out var data))
|
||||
return;
|
||||
|
||||
var usedEntity = (metaData.CollectionMaster != null) ? metaData.CollectionMaster : netEntity;
|
||||
var coords = _entManager.GetCoordinates(metaData.Coordinates);
|
||||
var texture = data.Item1;
|
||||
var color = data.Item2;
|
||||
var blink = usedEntity == _focusEntity;
|
||||
var modulator = Color.White;
|
||||
|
||||
if (_focusEntity != null && usedEntity != _focusEntity && !entitiesOfInterest.Contains(usedEntity.Value))
|
||||
modulator = Color.DimGray;
|
||||
|
||||
var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color * modulator, blink);
|
||||
NavMap.TrackedEntities[netEntity] = blip;
|
||||
}
|
||||
|
||||
private void SetTrackedEntityFromNavMap(NetEntity? netEntity)
|
||||
{
|
||||
if (netEntity == null)
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetComponent<PowerMonitoringConsoleComponent>(_owner, out var console))
|
||||
return;
|
||||
|
||||
if (!console.PowerMonitoringDeviceMetaData.TryGetValue(netEntity.Value, out var metaData))
|
||||
return;
|
||||
|
||||
// Switch entity for master, if applicable
|
||||
// The master will always be in the same group as the entity
|
||||
if (metaData.CollectionMaster != null)
|
||||
netEntity = metaData.CollectionMaster;
|
||||
|
||||
_focusEntity = netEntity;
|
||||
|
||||
// Switch tabs
|
||||
SwitchTabsBasedOnPowerMonitoringConsoleGroup(metaData.Group);
|
||||
|
||||
// Get the scroll position of the selected entity on the selected button the UI
|
||||
ActivateAutoScrollToFocus();
|
||||
|
||||
// Send message to console that the focus has changed
|
||||
SendPowerMonitoringConsoleMessageAction?.Invoke(_focusEntity, metaData.Group);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
AutoScrollToFocus();
|
||||
|
||||
// Warning sign pulse
|
||||
var lit = _gameTiming.RealTime.TotalSeconds % BlinkFrequency > BlinkFrequency / 2f;
|
||||
SystemWarningPanel.Modulate = lit ? Color.White : new Color(178, 178, 178);
|
||||
}
|
||||
|
||||
private PowerMonitoringConsoleEntry[] GetUpdatedPowerMonitoringConsoleEntries(PowerMonitoringConsoleEntry[] entries, PowerMonitoringConsoleComponent console)
|
||||
{
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
var entry = entries[i];
|
||||
|
||||
if (!console.PowerMonitoringDeviceMetaData.TryGetValue(entry.NetEntity, out var metaData))
|
||||
continue;
|
||||
|
||||
entries[i].MetaData = metaData;
|
||||
}
|
||||
|
||||
// Sort all devices alphabetically by their entity name (not by power usage; otherwise their position on the UI will shift)
|
||||
Array.Sort(entries, AlphabeticalSort);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private int AlphabeticalSort(PowerMonitoringConsoleEntry x, PowerMonitoringConsoleEntry y)
|
||||
{
|
||||
if (x.MetaData?.EntityName == null)
|
||||
return -1;
|
||||
|
||||
if (y.MetaData?.EntityName == null)
|
||||
return 1;
|
||||
|
||||
return x.MetaData.Value.EntityName.CompareTo(y.MetaData.Value.EntityName);
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class PowerMonitoringConsoleBoundUserInterface : ComputerBoundUserInterface<PowerMonitoringWindow, PowerMonitoringConsoleBoundInterfaceState>
|
||||
public struct PowerMonitoringConsoleTrackable
|
||||
{
|
||||
public PowerMonitoringConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
public EntityUid EntityUid;
|
||||
public PowerMonitoringConsoleGroup Group;
|
||||
|
||||
public PowerMonitoringConsoleTrackable(EntityUid uid, PowerMonitoringConsoleGroup group)
|
||||
{
|
||||
EntityUid = uid;
|
||||
Group = group;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.Warps;
|
||||
using Content.Shared.Pinpointer;
|
||||
@@ -32,7 +31,7 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
||||
SubscribeLocalEvent<StationGridAddedEvent>(OnStationInit);
|
||||
SubscribeLocalEvent<NavMapComponent, ComponentStartup>(OnNavMapStartup);
|
||||
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<NavMapComponent, GridSplitEvent>(OnNavMapSplit);
|
||||
SubscribeLocalEvent<GridSplitEvent>(OnNavMapSplit);
|
||||
|
||||
SubscribeLocalEvent<NavMapBeaconComponent, ComponentStartup>(OnNavMapBeaconStartup);
|
||||
SubscribeLocalEvent<NavMapBeaconComponent, AnchorStateChangedEvent>(OnNavMapBeaconAnchor);
|
||||
@@ -84,7 +83,7 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
||||
RefreshGrid(component, grid);
|
||||
}
|
||||
|
||||
private void OnNavMapSplit(EntityUid uid, NavMapComponent component, ref GridSplitEvent args)
|
||||
private void OnNavMapSplit(ref GridSplitEvent args)
|
||||
{
|
||||
var gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
|
||||
@@ -94,7 +93,7 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
||||
RefreshGrid(newComp, gridQuery.GetComponent(grid));
|
||||
}
|
||||
|
||||
RefreshGrid(component, gridQuery.GetComponent(uid));
|
||||
RefreshGrid(Comp<NavMapComponent>(args.Grid), gridQuery.GetComponent(args.Grid));
|
||||
}
|
||||
|
||||
private void RefreshGrid(NavMapComponent component, MapGridComponent grid)
|
||||
@@ -201,7 +200,6 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
||||
private void RefreshTile(MapGridComponent grid, NavMapComponent component, NavMapChunk chunk, Vector2i tile)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
|
||||
var existing = chunk.TileData;
|
||||
var flag = GetFlag(relative);
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tools;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using System.Diagnostics.Tracing;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
namespace Content.Server.Power.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Allows the attached entity to be destroyed by a cutting tool, dropping a piece of cable.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(CableSystem))]
|
||||
public sealed partial class CableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows the attached entity to be destroyed by a cutting tool, dropping a piece of cable.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(CableSystem))]
|
||||
public sealed partial class CableComponent : Component
|
||||
{
|
||||
[DataField("cableDroppedOnCutPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string CableDroppedOnCutPrototype = "CableHVStack1";
|
||||
|
||||
@@ -27,12 +29,26 @@ namespace Content.Server.Power.Components
|
||||
|
||||
[DataField("cuttingDelay")]
|
||||
public float CuttingDelay = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CableType
|
||||
/// <summary>
|
||||
/// Event to be raised when a cable is anchored / unanchored
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct CableAnchorStateChangedEvent
|
||||
{
|
||||
public readonly TransformComponent Transform;
|
||||
public EntityUid Entity => Transform.Owner;
|
||||
public bool Anchored => Transform.Anchored;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the entity is being detached to null-space
|
||||
/// </summary>
|
||||
public readonly bool Detaching;
|
||||
|
||||
public CableAnchorStateChangedEvent(TransformComponent transform, bool detaching = false)
|
||||
{
|
||||
HighVoltage,
|
||||
MediumVoltage,
|
||||
Apc,
|
||||
Detaching = detaching;
|
||||
Transform = transform;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Content.Shared.Power;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Content.Server.Power.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PowerMonitoringConsoleComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Power;
|
||||
|
||||
namespace Content.Server.Power.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used to flag any entities that should appear on a power monitoring console
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(PowerMonitoringConsoleSystem))]
|
||||
public sealed partial class PowerMonitoringDeviceComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the node that this device draws its power from (see <see cref="NodeContainerComponent"/>)
|
||||
/// </summary>
|
||||
[DataField("sourceNode"), ViewVariables]
|
||||
public string SourceNode = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the node that this device distributes power to (see <see cref="NodeContainerComponent"/>)
|
||||
/// </summary>
|
||||
[DataField("loadNode"), ViewVariables]
|
||||
public string LoadNode = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Names of the nodes that this device can potentially distributes power to (see <see cref="NodeContainerComponent"/>)
|
||||
/// </summary>
|
||||
[DataField("loadNodes"), ViewVariables]
|
||||
public List<string>? LoadNodes;
|
||||
|
||||
/// <summary>
|
||||
/// This entity will be grouped with entities that have the same collection name
|
||||
/// </summary>
|
||||
[DataField("collectionName"), ViewVariables]
|
||||
public string CollectionName = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
public BaseNodeGroup? NodeGroup = null;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the entity is/should be part of a collection
|
||||
/// </summary>
|
||||
public bool IsCollectionMasterOrChild { get { return CollectionName != string.Empty; } }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the uid of the master that represents this entity
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used when grouping multiple entities into a single power monitoring console entry
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public EntityUid CollectionMaster;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if this entity represents a group of entities
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used when grouping multiple entities into a single power monitoring console entry
|
||||
/// </remarks>
|
||||
public bool IsCollectionMaster { get { return Owner == CollectionMaster; } }
|
||||
|
||||
/// <summary>
|
||||
/// A list of other entities that are to be represented by this entity
|
||||
/// </summary>
|
||||
/// /// <remarks>
|
||||
/// Used when grouping multiple entities into a single power monitoring console entry
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public Dictionary<EntityUid, PowerMonitoringDeviceComponent> ChildDevices = new();
|
||||
|
||||
/// <summary>
|
||||
/// Path to the .rsi folder
|
||||
/// </summary>
|
||||
[DataField("sprite"), ViewVariables]
|
||||
public string SpritePath = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The .rsi state
|
||||
/// </summary>
|
||||
[DataField("state"), ViewVariables]
|
||||
public string SpriteState = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Determines what power monitoring group this entity should belong to
|
||||
/// </summary>
|
||||
[DataField("group", required: true), ViewVariables]
|
||||
public PowerMonitoringConsoleGroup Group;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Tools;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Shared.Map;
|
||||
using System.Xml.Schema;
|
||||
using CableCuttingFinishedEvent = Content.Shared.Tools.Systems.CableCuttingFinishedEvent;
|
||||
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
|
||||
|
||||
@@ -21,6 +22,7 @@ public sealed partial class CableSystem : EntitySystem
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
[Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogs = default!;
|
||||
[Dependency] private readonly PowerMonitoringConsoleSystem _powerMonitoringSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -47,17 +49,24 @@ public sealed partial class CableSystem : EntitySystem
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
var ev = new CableAnchorStateChangedEvent(xform);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
if (_electrocutionSystem.TryDoElectrifiedAct(uid, args.User))
|
||||
return;
|
||||
|
||||
_adminLogs.Add(LogType.CableCut, LogImpact.Medium, $"The {ToPrettyString(uid)} at {Transform(uid).Coordinates} was cut by {ToPrettyString(args.User)}.");
|
||||
_adminLogs.Add(LogType.CableCut, LogImpact.Medium, $"The {ToPrettyString(uid)} at {xform.Coordinates} was cut by {ToPrettyString(args.User)}.");
|
||||
|
||||
Spawn(cable.CableDroppedOnCutPrototype, Transform(uid).Coordinates);
|
||||
Spawn(cable.CableDroppedOnCutPrototype, xform.Coordinates);
|
||||
QueueDel(uid);
|
||||
}
|
||||
|
||||
private void OnAnchorChanged(EntityUid uid, CableComponent cable, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
var ev = new CableAnchorStateChangedEvent(args.Transform, args.Detaching);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
if (args.Anchored)
|
||||
return; // huh? it wasn't anchored?
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
@@ -8,6 +8,7 @@ public sealed partial class PowerGridCheckRuleComponent : Component
|
||||
{
|
||||
public CancellationTokenSource? AnnounceCancelToken;
|
||||
|
||||
public EntityUid AffectedStation;
|
||||
public readonly List<EntityUid> Powered = new();
|
||||
public readonly List<EntityUid> Unpowered = new();
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace Content.Server.StationEvents.Events
|
||||
if (!TryGetRandomStation(out var chosenStation))
|
||||
return;
|
||||
|
||||
component.AffectedStation = chosenStation.Value;
|
||||
|
||||
var query = AllEntityQuery<ApcComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var apcUid ,out var apc, out var transform))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Pinpointer;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Pinpointer;
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Power;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedPowerMonitoringConsoleSystem))]
|
||||
public sealed partial class PowerMonitoringCableNetworksComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A dictionary of the all the nav map chunks that contain anchored power cables
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public Dictionary<Vector2i, PowerCableChunk> AllChunks = new();
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary of the all the nav map chunks that contain anchored power cables
|
||||
/// that are directly connected to the console's current focus
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public Dictionary<Vector2i, PowerCableChunk> FocusChunks = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public struct PowerCableChunk
|
||||
{
|
||||
public readonly Vector2i Origin;
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask dictionary for power cables, 1 for occupied and 0 for empty.
|
||||
/// </summary>
|
||||
public int[] PowerCableData;
|
||||
|
||||
public PowerCableChunk(Vector2i origin)
|
||||
{
|
||||
Origin = origin;
|
||||
PowerCableData = new int[3];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Power
|
||||
{
|
||||
@@ -23,4 +23,12 @@ namespace Content.Shared.Power
|
||||
WireCount,
|
||||
CutWires
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum CableType
|
||||
{
|
||||
HighVoltage,
|
||||
MediumVoltage,
|
||||
Apc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,159 @@
|
||||
#nullable enable
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Power;
|
||||
|
||||
/// <summary>
|
||||
/// Flags an entity as being a power monitoring console
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedPowerMonitoringConsoleSystem), Other = AccessPermissions.ReadExecute)]
|
||||
public sealed partial class PowerMonitoringConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The EntityUid of the device that is the console's current focus
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not-networked - set by the console UI
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public EntityUid? Focus;
|
||||
|
||||
/// <summary>
|
||||
/// The group that the device that is the console's current focus belongs to
|
||||
/// </summary>
|
||||
/// /// <remarks>
|
||||
/// Not-networked - set by the console UI
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public PowerMonitoringConsoleGroup FocusGroup = PowerMonitoringConsoleGroup.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// A list of flags relating to currently active events of interest to the console.
|
||||
/// E.g., power sinks, power net anomalies
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public PowerMonitoringFlags Flags = PowerMonitoringFlags.None;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary containing all the meta data for tracked power monitoring devices
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public Dictionary<NetEntity, PowerMonitoringDeviceMetaData> PowerMonitoringDeviceMetaData = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public struct PowerMonitoringDeviceMetaData
|
||||
{
|
||||
public string EntityName;
|
||||
public NetCoordinates Coordinates;
|
||||
public PowerMonitoringConsoleGroup Group;
|
||||
public string SpritePath;
|
||||
public string SpriteState;
|
||||
public NetEntity? CollectionMaster;
|
||||
|
||||
public PowerMonitoringDeviceMetaData(string name, NetCoordinates coordinates, PowerMonitoringConsoleGroup group, string spritePath, string spriteState)
|
||||
{
|
||||
EntityName = name;
|
||||
Coordinates = coordinates;
|
||||
Group = group;
|
||||
SpritePath = spritePath;
|
||||
SpriteState = spriteState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data from by the server to the client for the power monitoring console UI
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PowerMonitoringConsoleBoundInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public double TotalSources;
|
||||
public double TotalBatteryUsage;
|
||||
public double TotalLoads;
|
||||
public PowerMonitoringConsoleEntry[] Sources;
|
||||
public PowerMonitoringConsoleEntry[] Loads;
|
||||
public PowerMonitoringConsoleBoundInterfaceState(double totalSources, double totalLoads, PowerMonitoringConsoleEntry[] sources, PowerMonitoringConsoleEntry[] loads)
|
||||
public PowerMonitoringConsoleEntry[] AllEntries;
|
||||
public PowerMonitoringConsoleEntry[] FocusSources;
|
||||
public PowerMonitoringConsoleEntry[] FocusLoads;
|
||||
|
||||
public PowerMonitoringConsoleBoundInterfaceState
|
||||
(double totalSources,
|
||||
double totalBatteryUsage,
|
||||
double totalLoads,
|
||||
PowerMonitoringConsoleEntry[] allEntries,
|
||||
PowerMonitoringConsoleEntry[] focusSources,
|
||||
PowerMonitoringConsoleEntry[] focusLoads)
|
||||
{
|
||||
TotalSources = totalSources;
|
||||
TotalBatteryUsage = totalBatteryUsage;
|
||||
TotalLoads = totalLoads;
|
||||
Sources = sources;
|
||||
Loads = loads;
|
||||
AllEntries = allEntries;
|
||||
FocusSources = focusSources;
|
||||
FocusLoads = focusLoads;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all the data needed to update a single device on the power monitoring UI
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PowerMonitoringConsoleEntry
|
||||
public struct PowerMonitoringConsoleEntry
|
||||
{
|
||||
public string NameLocalized;
|
||||
public string IconEntityPrototypeId;
|
||||
public double Size;
|
||||
public bool IsBattery;
|
||||
public PowerMonitoringConsoleEntry(string nl, string ipi, double size, bool isBattery)
|
||||
public NetEntity NetEntity;
|
||||
public PowerMonitoringConsoleGroup Group;
|
||||
public double PowerValue;
|
||||
|
||||
[NonSerialized] public PowerMonitoringDeviceMetaData? MetaData = null;
|
||||
|
||||
public PowerMonitoringConsoleEntry(NetEntity netEntity, PowerMonitoringConsoleGroup group, double powerValue = 0d)
|
||||
{
|
||||
NameLocalized = nl;
|
||||
IconEntityPrototypeId = ipi;
|
||||
Size = size;
|
||||
IsBattery = isBattery;
|
||||
NetEntity = netEntity;
|
||||
Group = group;
|
||||
PowerValue = powerValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the server to send updated power monitoring console data to the client for the single player session
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PowerMonitoringConsoleMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public NetEntity? FocusDevice;
|
||||
public PowerMonitoringConsoleGroup FocusGroup;
|
||||
|
||||
public PowerMonitoringConsoleMessage(NetEntity? focusDevice, PowerMonitoringConsoleGroup focusGroup)
|
||||
{
|
||||
FocusDevice = focusDevice;
|
||||
FocusGroup = focusGroup;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines how entities are grouped and color coded on the power monitor
|
||||
/// </summary>
|
||||
public enum PowerMonitoringConsoleGroup : byte
|
||||
{
|
||||
Generator,
|
||||
SMES,
|
||||
Substation,
|
||||
APC,
|
||||
Consumer,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PowerMonitoringFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
RoguePowerConsumer = 1,
|
||||
PowerNetAbnormalities = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI key associated with the power monitoring console
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum PowerMonitoringConsoleUiKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Power;
|
||||
|
||||
[UsedImplicitly]
|
||||
public abstract class SharedPowerMonitoringConsoleSystem : EntitySystem
|
||||
{
|
||||
}
|
||||
@@ -1,8 +1,27 @@
|
||||
power-monitoring-window-title = Power Monitoring Console
|
||||
power-monitoring-window-tab-sources = Sources
|
||||
power-monitoring-window-tab-loads = Loads
|
||||
power-monitoring-window-total-sources = Total Sources:
|
||||
power-monitoring-window-total-loads = Total Loads:
|
||||
|
||||
power-monitoring-window-label-sources = Sources
|
||||
power-monitoring-window-label-smes = SMES
|
||||
power-monitoring-window-label-substation = Substation
|
||||
power-monitoring-window-label-apc = APC
|
||||
power-monitoring-window-label-misc = Misc
|
||||
|
||||
power-monitoring-window-object-array = {$name} array [{$count}]
|
||||
|
||||
power-monitoring-window-station-name = [color=white][font size=14]{$stationName}[/font][/color]
|
||||
power-monitoring-window-unknown-location = Unknown location
|
||||
power-monitoring-window-total-sources = Total generator output
|
||||
power-monitoring-window-total-battery-usage = Total battery usage
|
||||
power-monitoring-window-total-loads = Total network loads
|
||||
power-monitoring-window-value = { POWERWATTS($value) }
|
||||
power-monitoring-window-show-inactive-consumers = Show Inactive Consumers
|
||||
|
||||
power-monitoring-window-show-cable-networks = Toggle cable networks
|
||||
power-monitoring-window-show-hv-cable = High voltage
|
||||
power-monitoring-window-show-mv-cable = Medium voltage
|
||||
power-monitoring-window-show-lv-cable = Low voltage
|
||||
|
||||
power-monitoring-window-flavor-left = [user@nanotrasen] $run power_net_query
|
||||
power-monitoring-window-flavor-right = v1.3
|
||||
power-monitoring-window-rogue-power-consumer = [color=white][font size=14][bold]! WARNING - ROGUE POWER CONSUMING DEVICE DETECTED ![/bold][/font][/color]
|
||||
power-monitoring-window-power-net-abnormalities = [color=white][font size=14][bold]CAUTION - ABNORMAL ACTIVITY IN POWER NET[/bold][/font][/color]
|
||||
|
||||
@@ -239,6 +239,7 @@
|
||||
- type: Computer
|
||||
board: PowerComputerCircuitboard
|
||||
- type: PowerMonitoringConsole
|
||||
- type: PowerMonitoringCableNetworks
|
||||
- type: NodeContainer
|
||||
examinable: true
|
||||
nodes:
|
||||
@@ -246,6 +247,7 @@
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
- type: ActivatableUI
|
||||
singleUser: true
|
||||
key: enum.PowerMonitoringConsoleUiKey.Key
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
|
||||
@@ -50,6 +50,12 @@
|
||||
input:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNode: input
|
||||
collectionName: radiationCollector
|
||||
sprite: Structures/Power/Generation/Singularity/collector.rsi
|
||||
state: static
|
||||
- type: RadiationCollector
|
||||
chargeModifier: 7500
|
||||
radiationReactiveGases:
|
||||
|
||||
@@ -78,8 +78,11 @@
|
||||
input:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
# - type: ApcPowerReceiver
|
||||
# - type: ExtensionCableReceiver
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNode: input
|
||||
sprite: Structures/Power/Generation/ame.rsi
|
||||
state: static
|
||||
- type: PowerSupplier
|
||||
supplyRate: 0
|
||||
- type: ContainerContainer
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNode: output
|
||||
sprite: Structures/Power/power.rsi
|
||||
state: generator
|
||||
- type: PowerSupplier
|
||||
supplyRate: 3000
|
||||
supplyRampRate: 500
|
||||
@@ -124,6 +129,11 @@
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNode: output
|
||||
sprite: Structures/Power/Generation/wallmount_generator.rsi
|
||||
state: static
|
||||
- type: PowerSupplier
|
||||
supplyRate: 3000
|
||||
supplyRampRate: 500
|
||||
@@ -229,6 +239,9 @@
|
||||
- type: Sprite
|
||||
sprite: Structures/Power/Generation/rtg.rsi
|
||||
state: rtg
|
||||
- type: PowerMonitoringDevice
|
||||
sprite: Structures/Power/Generation/rtg.rsi
|
||||
state: rtg
|
||||
- type: AmbientSound
|
||||
range: 5
|
||||
sound:
|
||||
@@ -272,6 +285,9 @@
|
||||
layers:
|
||||
- state: rtg_damaged
|
||||
- state: rtg_glow
|
||||
- type: PowerMonitoringDevice
|
||||
sprite: Structures/Power/Generation/rtg.rsi
|
||||
state: rtg_damaged
|
||||
- type: RadiationSource # ideally only when opened.
|
||||
intensity: 2
|
||||
- type: Destructible
|
||||
|
||||
@@ -170,6 +170,13 @@
|
||||
gasType: CarbonDioxide
|
||||
# 2 moles of gas for every sheet of plasma.
|
||||
moleRatio: 2
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNodes:
|
||||
- output_hv
|
||||
- output_mv
|
||||
sprite: Structures/Power/Generation/portable_generator.rsi
|
||||
state: portgen0
|
||||
|
||||
- type: entity
|
||||
name: S.U.P.E.R.P.A.C.M.A.N.-type portable generator
|
||||
@@ -219,6 +226,13 @@
|
||||
- type: UpgradePowerSupplier
|
||||
powerSupplyMultiplier: 1.25
|
||||
scaling: Exponential
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNodes:
|
||||
- output_hv
|
||||
- output_mv
|
||||
sprite: Structures/Power/Generation/portable_generator.rsi
|
||||
state: portgen1
|
||||
|
||||
- type: entity
|
||||
name: J.R.P.A.C.M.A.N.-type portable generator
|
||||
@@ -294,6 +308,11 @@
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: Apc
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNode: output
|
||||
sprite: Structures/Power/Generation/portable_generator.rsi
|
||||
state: portgen3
|
||||
- type: PowerSupplier
|
||||
# No ramping needed on this bugger.
|
||||
voltage: Apc
|
||||
|
||||
@@ -36,6 +36,12 @@
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNode: output
|
||||
sprite: Structures/Power/Generation/solar_panel.rsi
|
||||
state: static
|
||||
collectionName: SolarPanel
|
||||
- type: Anchorable
|
||||
- type: Pullable
|
||||
- type: Electrified
|
||||
|
||||
@@ -51,6 +51,11 @@
|
||||
teg:
|
||||
!type:TegNodeGenerator
|
||||
nodeGroupID: Teg
|
||||
- type: PowerMonitoringDevice
|
||||
group: Generator
|
||||
loadNode: output
|
||||
sprite: Structures/Power/Generation/teg.rsi
|
||||
state: static
|
||||
- type: Rotatable
|
||||
|
||||
# Note that only the TEG center is an AtmosDevice.
|
||||
|
||||
@@ -65,6 +65,13 @@
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: Apc
|
||||
- type: PowerMonitoringDevice
|
||||
group: APC
|
||||
sourceNode: input
|
||||
loadNode: output
|
||||
collectionName: apc
|
||||
sprite: Structures/Power/apc.rsi
|
||||
state: static
|
||||
- type: BatteryCharger
|
||||
voltage: Medium
|
||||
- type: PowerProvider
|
||||
|
||||
@@ -43,15 +43,22 @@
|
||||
examinable: true
|
||||
nodes:
|
||||
input:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
output:
|
||||
!type:CableTerminalPortNode
|
||||
nodeGroupID: HVPower
|
||||
- type: BatteryCharger
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: HVPower
|
||||
- type: PowerMonitoringDevice
|
||||
group: SMES
|
||||
sourceNode: input
|
||||
loadNode: output
|
||||
collectionName: smes
|
||||
sprite: Structures/Power/smes.rsi
|
||||
state: static
|
||||
- type: BatteryDischarger
|
||||
voltage: High
|
||||
node: output
|
||||
- type: BatteryDischarger
|
||||
- type: BatteryCharger
|
||||
voltage: High
|
||||
node: input
|
||||
- type: PowerNetworkBattery
|
||||
|
||||
@@ -40,6 +40,13 @@
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: MVPower
|
||||
- type: PowerMonitoringDevice
|
||||
group: Substation
|
||||
sourceNode: input
|
||||
loadNode: output
|
||||
collectionName: substation
|
||||
sprite: Structures/Power/substation.rsi
|
||||
state: substation_static
|
||||
- type: BatteryCharger
|
||||
voltage: High
|
||||
- type: BatteryDischarger
|
||||
@@ -166,6 +173,12 @@
|
||||
output:
|
||||
!type:CableDeviceNode
|
||||
nodeGroupID: MVPower
|
||||
- type: PowerMonitoringDevice
|
||||
group: Substation
|
||||
sourceNode: input
|
||||
loadNode: output
|
||||
sprite: Structures/Power/substation.rsi
|
||||
state: substation_wall_static
|
||||
- type: BatteryCharger
|
||||
voltage: High
|
||||
- type: BatteryDischarger
|
||||
|
||||
BIN
Resources/Textures/Interface/NavMap/beveled_hexagon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Resources/Textures/Interface/NavMap/beveled_square.png
Normal file
|
After Width: | Height: | Size: 904 B |
BIN
Resources/Textures/Interface/NavMap/beveled_triangle.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Resources/Textures/Interface/PowerMonitoring/load_arrow.png
Normal file
|
After Width: | Height: | Size: 826 B |
BIN
Resources/Textures/Interface/PowerMonitoring/source_arrow.png
Normal file
|
After Width: | Height: | Size: 774 B |
@@ -12,6 +12,9 @@
|
||||
},
|
||||
{
|
||||
"name": "ca_on"
|
||||
},
|
||||
{
|
||||
"name": "static"
|
||||
},
|
||||
{
|
||||
"name": "ca_active",
|
||||
|
||||
|
After Width: | Height: | Size: 664 B |
@@ -86,6 +86,9 @@
|
||||
},
|
||||
{
|
||||
"name": "control"
|
||||
},
|
||||
{
|
||||
"name": "static"
|
||||
},
|
||||
{
|
||||
"name": "control_critical",
|
||||
|
||||
|
After Width: | Height: | Size: 618 B |
@@ -1 +1,30 @@
|
||||
{"version":1,"license":"CC-BY-SA-3.0","copyright":"Taken from https://github.com/discordia-space/CEV-Eris/blob/d1e0161af146835f4fb79d21a6200caa9cc842d0/icons/obj/power.dmi and modified.","size":{"x":32,"y":32},"states":[{"name":"normal","select":[],"flags":{},"directions":8},{"name":"broken","select":[],"flags":{},"directions":1},{"name": "solar_assembly"},{"name": "solar_tracker"}]}
|
||||
{
|
||||
"version":1,
|
||||
"license":"CC-BY-SA-3.0",
|
||||
"copyright":"Taken from https://github.com/discordia-space/CEV-Eris/blob/d1e0161af146835f4fb79d21a6200caa9cc842d0/icons/obj/power.dmi and modified.",
|
||||
"size":{"x":32,"y":32},
|
||||
"states":
|
||||
[
|
||||
{
|
||||
"name": "normal",
|
||||
"select": [],
|
||||
"flags": {},
|
||||
"directions": 8
|
||||
},
|
||||
{
|
||||
"name": "broken",
|
||||
"select": [],
|
||||
"flags": {},
|
||||
"directions": 1
|
||||
},
|
||||
{
|
||||
"name": "static"
|
||||
},
|
||||
{
|
||||
"name": "solar_assembly"
|
||||
},
|
||||
{
|
||||
"name": "solar_tracker"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 223 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "teg",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "static"
|
||||
},
|
||||
{
|
||||
"name": "teg-op1",
|
||||
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -9,6 +9,9 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "panel"
|
||||
},
|
||||
{
|
||||
"name": "static"
|
||||
},
|
||||
{
|
||||
"name": "on",
|
||||
|
||||
|
After Width: | Height: | Size: 335 B |
@@ -9,6 +9,9 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "static"
|
||||
},
|
||||
{
|
||||
"name": "broken"
|
||||
|
||||
BIN
Resources/Textures/Structures/Power/apc.rsi/static.png
Normal file
|
After Width: | Height: | Size: 384 B |
@@ -9,6 +9,9 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "smes"
|
||||
},
|
||||
{
|
||||
"name": "static"
|
||||
},
|
||||
{
|
||||
"name": "smes-open"
|
||||
|
||||
BIN
Resources/Textures/Structures/Power/smes.rsi/static.png
Normal file
|
After Width: | Height: | Size: 884 B |
@@ -10,9 +10,15 @@
|
||||
{
|
||||
"name": "substation"
|
||||
},
|
||||
{
|
||||
"name": "substation_static"
|
||||
},
|
||||
{
|
||||
"name": "substation_wall"
|
||||
},
|
||||
{
|
||||
"name": "substation_wall_static"
|
||||
},
|
||||
{
|
||||
"name": "full"
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 444 B |