Compare commits

..

17 Commits

Author SHA1 Message Date
comasqw
fff4287ad5 ревёрт "demiplane modifiers", всё же нужно делать биом 2024-12-12 01:07:35 +04:00
comasqw
d1e4501b6f Merge branch 'master' into MagicWands 2024-12-12 00:59:47 +04:00
comasqw
fc4f51f0dd demiplane modifiers 2024-12-12 00:58:55 +04:00
comasqw
06cb93fb1d recipes for workbench 2024-12-11 16:34:09 +04:00
comasqw
14dbb5d3ed added all modular parts 2024-12-11 13:27:33 +04:00
comasqw
337d248884 added magic crystals for all spells 2024-12-11 00:15:19 +04:00
comasqw
596c329c96 new staff 2024-12-10 17:37:43 +04:00
comasqw
bae34b8318 bup 2024-12-10 01:58:47 +04:00
comasqw
e7e4e61972 magic artifacts 2024-12-10 01:57:41 +04:00
comasqw
fc90485838 Merge branch 'master' into MagicWands 2024-12-09 21:35:22 +04:00
comasqw
ac5579c7fd added base old magic scroll 2024-12-09 21:34:28 +04:00
comasqw
cd618efc31 update crystals modalrPart structure 2024-12-09 20:59:59 +04:00
comasqw
6b41aa71ca change ManacostModify 2024-12-05 01:23:47 +04:00
comasqw
a4395f2066 Merge branch 'master' into MagicWands 2024-12-05 00:46:27 +04:00
comasqw
685a9616ab много чего 2024-12-05 00:41:23 +04:00
comasqw
86168288a3 Merge branch 'master' into MagicWands 2024-12-02 12:34:39 +04:00
comasqw
81dd06ac2c init 2024-12-02 12:31:07 +04:00
1948 changed files with 219022 additions and 671468 deletions

37
.github/CODEOWNERS vendored
View File

@@ -2,30 +2,49 @@
# Sorting by path instead of by who added it one day :(
# this isn't how codeowners rules work pls read the first comment instead of trying to force a sorting order
/Resources/ConfigPresets/WizardsDen/ @Chief-Engineer
/Resources/ConfigPresets/WizardsDen/ @nikthechampiongr @crazybrain23
/Content.*/Administration/ @DrSmugleaf @nikthechampiongr @crazybrain23
/Resources/ServerInfo/ @nikthechampiongr @crazybrain23
/Resources/ServerInfo/Guidebook/ServerRules/ @nikthechampiongr @crazybrain23
# Moony's Gargantuan List Of Things She Cares About, or MGLOTSCA for short.
# You need to add your name to these entries, not make a new one, if you care about them.
/Content.*/Toolshed/ @moonheart08
**/Toolshed/** @moonheart08
*Command.cs @moonheart08
/Content.*/Administration/ @moonheart08 @DrSmugleaf @Chief-Engineer
/Content.*/Station/ @moonheart08
/Content.*/Maps/ @moonheart08
/Content.*/GameTicking/ @moonheart08 @EmoGarbage404
/Resources/ServerInfo/ @moonheart08 @Chief-Engineer
/Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404
/Resources/ServerInfo/Guidebook/ServerRules/ @Chief-Engineer
/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/Prototypes/Maps/** @Emisse
/Resources/Prototypes/Body/ @DrSmugleaf # suffering
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf
/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf
/Resources/Prototypes/Guidebook/rules.yml @nikthechampiongr @crazybrain23
/Resources/Prototypes/Guidebook/rules.yml @Chief-Engineer
/Content.*/Body/ @DrSmugleaf
/Content.YAMLLinter @DrSmugleaf
/Content.Shared/Damage/ @DrSmugleaf
/Content.*/Anomaly/ @TheShuEd
/Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @TheShuEd
/Content.*/Anomaly/ @EmoGarbage404 @TheShuEd
/Content.*/Lathe/ @EmoGarbage404
/Content.*/Materials/ @EmoGarbage404
/Content.*/Mech/ @EmoGarbage404
/Content.*/Research/ @EmoGarbage404
/Content.*/Stack/ @EmoGarbage404
/Content.*/Xenoarchaeology/ @EmoGarbage404
/Content.*/Zombies/ @EmoGarbage404
/Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @EmoGarbage404 @TheShuEd
/Resources/Prototypes/Research/ @EmoGarbage404
/Content.*/Forensics/ @ficcialfaint
# SKREEEE
/Content.*.Database/ @PJB3005 @DrSmugleaf
/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @nikthechampiongr @crazybrain23
/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @Chief-Engineer
/Pow3r/ @PJB3005
/Content.Server/Power/Pow3r/ @PJB3005
@@ -33,7 +52,7 @@
/Content.*/Atmos/ @Partmedia
/Content.*/Botany/ @Partmedia
# Jezi
#Jezi
/Content.*/Medical @Jezithyr
/Content.*/Body @Jezithyr

4
.github/labeler.yml vendored
View File

@@ -16,10 +16,6 @@
- changed-files:
- any-glob-to-any-file: '**/*.swsl'
"Changes: Audio":
- changed-files:
- any-glob-to-any-file: '**/*.ogg'
"Changes: No C#":
- changed-files:
# Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label.

View File

@@ -2,11 +2,11 @@
on:
push:
branches: [ master, staging, stable ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
branches: [ master, staging, stable ]
branches: [ master ]
jobs:
build:

View File

@@ -2,11 +2,11 @@ name: Build & Test Debug
on:
push:
branches: [ master, staging, stable ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
branches: [ master, staging, stable ]
branches: [ master ]
jobs:
build:

View File

@@ -14,7 +14,7 @@ jobs:
{
"0": "XS",
"10": "S",
"100": "M",
"1000": "L",
"5000": "XL"
"30": "M",
"100": "L",
"1000": "XL"
}

View File

@@ -2,7 +2,7 @@
on:
push:
branches: [ master, staging, stable ]
branches: [ master, staging, trying ]
paths:
- '**.cs'
- '**.csproj'
@@ -16,7 +16,7 @@ on:
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
branches: [ master, staging, stable ]
branches: [ master ]
paths:
- '**.cs'
- '**.csproj'

View File

@@ -1,7 +1,7 @@
name: RGA schema validator
on:
push:
branches: [ master, staging, stable ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]

View File

@@ -2,7 +2,7 @@ name: RSI Validator
on:
push:
branches: [ master, staging, stable ]
branches: [ staging, trying ]
merge_group:
pull_request:
paths:

View File

@@ -1,7 +1,7 @@
name: Map file schema validator
on:
push:
branches: [ master, staging, stable ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]

View File

@@ -2,7 +2,7 @@ name: YAML Linter
on:
push:
branches: [ master, staging, stable ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]

View File

@@ -31,6 +31,19 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
[AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state",
};
private Dictionary<Gas, string> _gasShorthands = new Dictionary<Gas, string>()
{
[Gas.Ammonia] = "NH₃",
[Gas.CarbonDioxide] = "CO₂",
[Gas.Frezon] = "F",
[Gas.Nitrogen] = "N₂",
[Gas.NitrousOxide] = "N₂O",
[Gas.Oxygen] = "O₂",
[Gas.Plasma] = "P",
[Gas.Tritium] = "T",
[Gas.WaterVapor] = "H₂O",
};
public AtmosAlarmEntryContainer(NetEntity uid, EntityCoordinates? coordinates)
{
RobustXamlLoader.Load(this);
@@ -149,11 +162,12 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs)
{
FixedPoint2 gasPercent = percent * 100f;
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
var gasShorthand = _gasShorthands.GetValueOrDefault(gas, "X");
var gasLabel = new Label()
{
Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasAbbreviation), ("value", gasPercent)),
Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasShorthand), ("value", gasPercent)),
FontOverride = normalFont,
FontColorOverride = GetAlarmStateColor(alert),
HorizontalAlignment = HAlignment.Center,

View File

@@ -1,40 +0,0 @@
using Content.Shared.Atmos.Components;
namespace Content.Client.Atmos.Consoles;
public sealed class AtmosMonitoringConsoleBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private AtmosMonitoringConsoleWindow? _menu;
public AtmosMonitoringConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
protected override void Open()
{
base.Open();
_menu = new AtmosMonitoringConsoleWindow(this, Owner);
_menu.OpenCentered();
_menu.OnClose += Close;
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not AtmosMonitoringConsoleBoundInterfaceState castState)
return;
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
_menu?.UpdateUI(xform?.Coordinates, castState.AtmosNetworks);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Dispose();
}
}

View File

@@ -1,295 +0,0 @@
using Content.Client.Pinpointer.UI;
using Content.Shared.Atmos.Components;
using Content.Shared.Pinpointer;
using Robust.Client.Graphics;
using Robust.Shared.Collections;
using Robust.Shared.Map.Components;
using System.Linq;
using System.Numerics;
namespace Content.Client.Atmos.Consoles;
public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
{
[Dependency] private readonly IEntityManager _entManager = default!;
public bool ShowPipeNetwork = true;
public int? FocusNetId = null;
private const int ChunkSize = 4;
private readonly Color _basePipeNetColor = Color.LightGray;
private readonly Color _unfocusedPipeNetColor = Color.DimGray;
private List<AtmosMonitoringConsoleLine> _atmosPipeNetwork = new();
private Dictionary<Color, Color> _sRGBLookUp = new Dictionary<Color, Color>();
// Look up tables for merging continuous lines. Indexed by line color
private Dictionary<Color, Dictionary<Vector2i, Vector2i>> _horizLines = new();
private Dictionary<Color, Dictionary<Vector2i, Vector2i>> _horizLinesReversed = new();
private Dictionary<Color, Dictionary<Vector2i, Vector2i>> _vertLines = new();
private Dictionary<Color, Dictionary<Vector2i, Vector2i>> _vertLinesReversed = new();
public AtmosMonitoringConsoleNavMapControl() : base()
{
PostWallDrawingAction += DrawAllPipeNetworks;
}
protected override void UpdateNavMap()
{
base.UpdateNavMap();
if (!_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(Owner, out var console))
return;
if (!_entManager.TryGetComponent<MapGridComponent>(MapUid, out var grid))
return;
_atmosPipeNetwork = GetDecodedAtmosPipeChunks(console.AtmosPipeChunks, grid);
}
private void DrawAllPipeNetworks(DrawingHandleScreen handle)
{
if (!ShowPipeNetwork)
return;
// Draw networks
if (_atmosPipeNetwork != null && _atmosPipeNetwork.Any())
DrawPipeNetwork(handle, _atmosPipeNetwork);
}
private void DrawPipeNetwork(DrawingHandleScreen handle, List<AtmosMonitoringConsoleLine> atmosPipeNetwork)
{
var offset = GetOffset();
offset = offset with { Y = -offset.Y };
if (WorldRange / WorldMaxRange > 0.5f)
{
var pipeNetworks = new Dictionary<Color, ValueList<Vector2>>();
foreach (var chunkedLine in atmosPipeNetwork)
{
var start = ScalePosition(chunkedLine.Origin - offset);
var end = ScalePosition(chunkedLine.Terminus - offset);
if (!pipeNetworks.TryGetValue(chunkedLine.Color, out var subNetwork))
subNetwork = new ValueList<Vector2>();
subNetwork.Add(start);
subNetwork.Add(end);
pipeNetworks[chunkedLine.Color] = subNetwork;
}
foreach ((var color, var subNetwork) in pipeNetworks)
{
if (subNetwork.Count > 0)
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, subNetwork.Span, color);
}
}
else
{
var pipeVertexUVs = new Dictionary<Color, ValueList<Vector2>>();
foreach (var chunkedLine in atmosPipeNetwork)
{
var leftTop = ScalePosition(new Vector2
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
- offset);
var rightTop = ScalePosition(new Vector2
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
- offset);
var leftBottom = ScalePosition(new Vector2
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
- offset);
var rightBottom = ScalePosition(new Vector2
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
- offset);
if (!pipeVertexUVs.TryGetValue(chunkedLine.Color, out var pipeVertexUV))
pipeVertexUV = new ValueList<Vector2>();
pipeVertexUV.Add(leftBottom);
pipeVertexUV.Add(leftTop);
pipeVertexUV.Add(rightBottom);
pipeVertexUV.Add(leftTop);
pipeVertexUV.Add(rightBottom);
pipeVertexUV.Add(rightTop);
pipeVertexUVs[chunkedLine.Color] = pipeVertexUV;
}
foreach ((var color, var pipeVertexUV) in pipeVertexUVs)
{
if (pipeVertexUV.Count > 0)
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, pipeVertexUV.Span, color);
}
}
}
private List<AtmosMonitoringConsoleLine> GetDecodedAtmosPipeChunks(Dictionary<Vector2i, AtmosPipeChunk>? chunks, MapGridComponent? grid)
{
var decodedOutput = new List<AtmosMonitoringConsoleLine>();
if (chunks == null || grid == null)
return decodedOutput;
// Clear stale look up table values
_horizLines.Clear();
_horizLinesReversed.Clear();
_vertLines.Clear();
_vertLinesReversed.Clear();
// Generate masks
var northMask = (ulong)1 << 0;
var southMask = (ulong)1 << 1;
var westMask = (ulong)1 << 2;
var eastMask = (ulong)1 << 3;
foreach ((var chunkOrigin, var chunk) in chunks)
{
var list = new List<AtmosMonitoringConsoleLine>();
foreach (var ((netId, hexColor), atmosPipeData) in chunk.AtmosPipeData)
{
// Determine the correct coloration for the pipe
var color = Color.FromHex(hexColor) * _basePipeNetColor;
if (FocusNetId != null && FocusNetId != netId)
color *= _unfocusedPipeNetColor;
// Get the associated line look up tables
if (!_horizLines.TryGetValue(color, out var horizLines))
{
horizLines = new();
_horizLines[color] = horizLines;
}
if (!_horizLinesReversed.TryGetValue(color, out var horizLinesReversed))
{
horizLinesReversed = new();
_horizLinesReversed[color] = horizLinesReversed;
}
if (!_vertLines.TryGetValue(color, out var vertLines))
{
vertLines = new();
_vertLines[color] = vertLines;
}
if (!_vertLinesReversed.TryGetValue(color, out var vertLinesReversed))
{
vertLinesReversed = new();
_vertLinesReversed[color] = vertLinesReversed;
}
// Loop over the chunk
for (var tileIdx = 0; tileIdx < ChunkSize * ChunkSize; tileIdx++)
{
if (atmosPipeData == 0)
continue;
var mask = (ulong)SharedNavMapSystem.AllDirMask << tileIdx * SharedNavMapSystem.Directions;
if ((atmosPipeData & mask) == 0)
continue;
var relativeTile = GetTileFromIndex(tileIdx);
var tile = (chunk.Origin * ChunkSize + relativeTile) * grid.TileSize;
tile = tile with { Y = -tile.Y };
// Calculate the draw point offsets
var vertLineOrigin = (atmosPipeData & northMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 1f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
var vertLineTerminus = (atmosPipeData & southMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
var horizLineOrigin = (atmosPipeData & eastMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 1f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
var horizLineTerminus = (atmosPipeData & westMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
// Since we can have pipe lines that have a length of a half tile,
// double the vectors and convert to vector2i so we can merge them
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + horizLineOrigin, 2), ConvertVector2ToVector2i(tile + horizLineTerminus, 2), horizLines, horizLinesReversed);
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + vertLineOrigin, 2), ConvertVector2ToVector2i(tile + vertLineTerminus, 2), vertLines, vertLinesReversed);
}
}
}
// Scale the vector2is back down and convert to vector2
foreach (var (color, horizLines) in _horizLines)
{
// Get the corresponding sRBG color
var sRGB = GetsRGBColor(color);
foreach (var (origin, terminal) in horizLines)
decodedOutput.Add(new AtmosMonitoringConsoleLine
(ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB));
}
foreach (var (color, vertLines) in _vertLines)
{
// Get the corresponding sRBG color
var sRGB = GetsRGBColor(color);
foreach (var (origin, terminal) in vertLines)
decodedOutput.Add(new AtmosMonitoringConsoleLine
(ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB));
}
return decodedOutput;
}
private Vector2 ConvertVector2iToVector2(Vector2i vector, float scale = 1f)
{
return new Vector2(vector.X * scale, vector.Y * scale);
}
private Vector2i ConvertVector2ToVector2i(Vector2 vector, float scale = 1f)
{
return new Vector2i((int)MathF.Round(vector.X * scale), (int)MathF.Round(vector.Y * scale));
}
private Vector2i GetTileFromIndex(int index)
{
var x = index / ChunkSize;
var y = index % ChunkSize;
return new Vector2i(x, y);
}
private Color GetsRGBColor(Color color)
{
if (!_sRGBLookUp.TryGetValue(color, out var sRGB))
{
sRGB = Color.ToSrgb(color);
_sRGBLookUp[color] = sRGB;
}
return sRGB;
}
}
public struct AtmosMonitoringConsoleLine
{
public readonly Vector2 Origin;
public readonly Vector2 Terminus;
public readonly Color Color;
public AtmosMonitoringConsoleLine(Vector2 origin, Vector2 terminus, Color color)
{
Origin = origin;
Terminus = terminus;
Color = color;
}
}

View File

@@ -1,69 +0,0 @@
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Consoles;
using Robust.Shared.GameStates;
namespace Content.Client.Atmos.Consoles;
public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosMonitoringConsoleComponent, ComponentHandleState>(OnHandleState);
}
private void OnHandleState(EntityUid uid, AtmosMonitoringConsoleComponent component, ref ComponentHandleState args)
{
Dictionary<Vector2i, Dictionary<(int, string), ulong>> modifiedChunks;
Dictionary<NetEntity, AtmosDeviceNavMapData> atmosDevices;
switch (args.Current)
{
case AtmosMonitoringConsoleDeltaState delta:
{
modifiedChunks = delta.ModifiedChunks;
atmosDevices = delta.AtmosDevices;
foreach (var index in component.AtmosPipeChunks.Keys)
{
if (!delta.AllChunks!.Contains(index))
component.AtmosPipeChunks.Remove(index);
}
break;
}
case AtmosMonitoringConsoleState state:
{
modifiedChunks = state.Chunks;
atmosDevices = state.AtmosDevices;
foreach (var index in component.AtmosPipeChunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
component.AtmosPipeChunks.Remove(index);
}
break;
}
default:
return;
}
foreach (var (origin, chunk) in modifiedChunks)
{
var newChunk = new AtmosPipeChunk(origin);
newChunk.AtmosPipeData = new Dictionary<(int, string), ulong>(chunk);
component.AtmosPipeChunks[origin] = newChunk;
}
component.AtmosDevices.Clear();
foreach (var (nuid, atmosDevice) in atmosDevices)
{
component.AtmosDevices[nuid] = atmosDevice;
}
}
}

View File

@@ -1,99 +0,0 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Atmos.Consoles"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'atmos-monitoring-window-title'}"
Resizable="False"
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:AtmosMonitoringConsoleNavMapControl 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:AtmosMonitoringConsoleNavMapControl>
<!-- Nav map legend -->
<BoxContainer Orientation="Horizontal" Margin="0 10 0 10">
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_square.png"
Modulate="#a9a9a9"
SetSize="16 16"
Margin="20 0 5 0"/>
<Label Text="{Loc 'atmos-monitoring-window-label-gas-opening'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
SetSize="16 16"
Modulate="#a9a9a9"
Margin="20 0 5 0"/>
<Label Text="{Loc 'atmos-monitoring-window-label-gas-scrubber'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_arrow_east.png"
SetSize="16 16"
Modulate="#a9a9a9"
Margin="20 0 5 0"/>
<Label Text="{Loc 'atmos-monitoring-window-label-gas-flow-regulator'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_hexagon.png"
SetSize="16 16"
Modulate="#a9a9a9"
Margin="20 0 5 0"/>
<Label Text="{Loc 'atmos-monitoring-window-label-thermoregulator'}"/>
</BoxContainer>
</BoxContainer>
<!-- Atmosphere 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>
<!-- Alarm status (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="AtmosNetworksTable" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 0 10"/>
</ScrollContainer>
</TabContainer>
<!-- Overlay toggles -->
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
<Label Text="{Loc 'atmos-monitoring-window-toggle-overlays'}" Margin="0 0 0 5"/>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<CheckBox Name="ShowPipeNetwork" Text="{Loc 'atmos-monitoring-window-show-pipe-network'}" Pressed="True" HorizontalExpand="True"/>
<CheckBox Name="ShowGasPipeSensors" Text="{Loc 'atmos-monitoring-window-show-gas-pipe-sensors'}" Pressed="False" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<!-- Footer -->
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'atmos-monitoring-window-flavor-left'}" StyleClasses="WindowFooterText" />
<Label Text="{Loc 'atmos-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>

View File

@@ -1,455 +0,0 @@
using Content.Client.Pinpointer.UI;
using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos.Components;
using Content.Shared.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Content.Client.Atmos.Consoles;
[GenerateTypedNameReferences]
public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow
{
private readonly IEntityManager _entManager;
private readonly IPrototypeManager _protoManager;
private readonly SpriteSystem _spriteSystem;
private EntityUid? _owner;
private NetEntity? _focusEntity;
private int? _focusNetId;
private bool _autoScrollActive = false;
private readonly Color _unfocusedDeviceColor = Color.DimGray;
private ProtoId<NavMapBlipPrototype> _navMapConsoleProtoId = "NavMapConsole";
private ProtoId<NavMapBlipPrototype> _gasPipeSensorProtoId = "GasPipeSensor";
public AtmosMonitoringConsoleWindow(AtmosMonitoringConsoleBoundUserInterface userInterface, EntityUid? owner)
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_protoManager = IoCManager.Resolve<IPrototypeManager>();
_spriteSystem = _entManager.System<SpriteSystem>();
// Pass the owner to nav map
_owner = owner;
NavMap.Owner = _owner;
// Set nav map grid uid
var stationName = Loc.GetString("atmos-monitoring-window-unknown-location");
EntityCoordinates? consoleCoords = null;
if (_entManager.TryGetComponent<TransformComponent>(owner, out var xform))
{
consoleCoords = xform.Coordinates;
NavMap.MapUid = xform.GridUid;
// Assign station name
if (_entManager.TryGetComponent<MetaDataComponent>(xform.GridUid, out var stationMetaData))
stationName = stationMetaData.EntityName;
var msg = new FormattedMessage();
msg.TryAddMarkup(Loc.GetString("atmos-monitoring-window-station-name", ("stationName", stationName)), out _);
StationName.SetMessage(msg);
}
else
{
StationName.SetMessage(stationName);
NavMap.Visible = false;
}
// Set trackable entity selected action
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
// Update nav map
NavMap.ForceNavMapUpdate();
// Set tab container headers
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-monitoring-window-tab-networks"));
// Set UI toggles
ShowPipeNetwork.OnToggled += _ => OnShowPipeNetworkToggled();
ShowGasPipeSensors.OnToggled += _ => OnShowGasPipeSensors();
// Set nav map colors
if (!_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(_owner, out var console))
return;
NavMap.TileColor = console.NavMapTileColor;
NavMap.WallColor = console.NavMapWallColor;
// Initalize
UpdateUI(consoleCoords, Array.Empty<AtmosMonitoringConsoleEntry>());
}
#region Toggle handling
private void OnShowPipeNetworkToggled()
{
if (_owner == null)
return;
if (!_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(_owner.Value, out var console))
return;
NavMap.ShowPipeNetwork = ShowPipeNetwork.Pressed;
foreach (var (netEnt, device) in console.AtmosDevices)
{
if (device.NavMapBlip == _gasPipeSensorProtoId)
continue;
if (ShowPipeNetwork.Pressed)
AddTrackedEntityToNavMap(device);
else
NavMap.TrackedEntities.Remove(netEnt);
}
}
private void OnShowGasPipeSensors()
{
if (_owner == null)
return;
if (!_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(_owner.Value, out var console))
return;
foreach (var (netEnt, device) in console.AtmosDevices)
{
if (device.NavMapBlip != _gasPipeSensorProtoId)
continue;
if (ShowGasPipeSensors.Pressed)
AddTrackedEntityToNavMap(device, true);
else
NavMap.TrackedEntities.Remove(netEnt);
}
}
#endregion
public void UpdateUI
(EntityCoordinates? consoleCoords,
AtmosMonitoringConsoleEntry[] atmosNetworks)
{
if (_owner == null)
return;
if (!_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(_owner.Value, out var console))
return;
// Reset nav map values
NavMap.TrackedCoordinates.Clear();
NavMap.TrackedEntities.Clear();
if (_focusEntity != null && !console.AtmosDevices.Any(x => x.Key == _focusEntity))
ClearFocus();
// Add tracked entities to the nav map
UpdateNavMapBlips();
// Show the monitor location
var consoleNetEnt = _entManager.GetNetEntity(_owner);
if (consoleCoords != null && consoleNetEnt != null)
{
var proto = _protoManager.Index(_navMapConsoleProtoId);
if (proto.TexturePaths != null && proto.TexturePaths.Length != 0)
{
var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(proto.TexturePaths[0]));
var blip = new NavMapBlip(consoleCoords.Value, texture, proto.Color, proto.Blinks, proto.Selectable);
NavMap.TrackedEntities[consoleNetEnt.Value] = blip;
}
}
// Update the nav map
NavMap.ForceNavMapUpdate();
// Clear excess children from the tables
while (AtmosNetworksTable.ChildCount > atmosNetworks.Length)
AtmosNetworksTable.RemoveChild(AtmosNetworksTable.GetChild(AtmosNetworksTable.ChildCount - 1));
// Update all entries in each table
for (int index = 0; index < atmosNetworks.Length; index++)
{
var entry = atmosNetworks.ElementAt(index);
UpdateUIEntry(entry, index, AtmosNetworksTable, console);
}
}
private void UpdateNavMapBlips()
{
if (_owner == null || !_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(_owner.Value, out var console))
return;
if (NavMap.Visible)
{
foreach (var (netEnt, device) in console.AtmosDevices)
{
// Update the focus network ID, incase it has changed
if (_focusEntity == netEnt)
{
_focusNetId = device.NetId;
NavMap.FocusNetId = _focusNetId;
}
var isSensor = device.NavMapBlip == _gasPipeSensorProtoId;
// Skip network devices if the toggled is off
if (!ShowPipeNetwork.Pressed && !isSensor)
continue;
// Skip gas pipe sensors if the toggle is off
if (!ShowGasPipeSensors.Pressed && isSensor)
continue;
AddTrackedEntityToNavMap(device, isSensor);
}
}
}
private void AddTrackedEntityToNavMap(AtmosDeviceNavMapData metaData, bool isSensor = false)
{
var proto = _protoManager.Index(metaData.NavMapBlip);
if (proto.TexturePaths == null || proto.TexturePaths.Length == 0)
return;
var idx = Math.Clamp((int)metaData.Direction / 2, 0, proto.TexturePaths.Length - 1);
var texture = proto.TexturePaths.Length > 0 ? proto.TexturePaths[idx] : proto.TexturePaths[0];
var color = isSensor ? proto.Color : proto.Color * metaData.PipeColor;
if (_focusNetId != null && metaData.NetId != _focusNetId)
color *= _unfocusedDeviceColor;
var blinks = proto.Blinks || _focusEntity == metaData.NetEntity;
var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
var blip = new NavMapBlip(coords, _spriteSystem.Frame0(new SpriteSpecifier.Texture(texture)), color, blinks, proto.Selectable, proto.Scale);
NavMap.TrackedEntities[metaData.NetEntity] = blip;
}
private void UpdateUIEntry(AtmosMonitoringConsoleEntry data, int index, Control table, AtmosMonitoringConsoleComponent console)
{
// Make new UI entry if required
if (index >= table.ChildCount)
{
var newEntryContainer = new AtmosMonitoringEntryContainer(data);
// On click
newEntryContainer.FocusButton.OnButtonUp += args =>
{
if (_focusEntity == newEntryContainer.Data.NetEntity)
{
ClearFocus();
}
else
{
SetFocus(newEntryContainer.Data.NetEntity, newEntryContainer.Data.NetId);
var coords = _entManager.GetCoordinates(newEntryContainer.Data.Coordinates);
NavMap.CenterToCoordinates(coords);
}
// Update affected UI elements across all tables
UpdateConsoleTable(console, AtmosNetworksTable, _focusEntity);
};
// Add the entry to the current table
table.AddChild(newEntryContainer);
}
// Update values and UI elements
var tableChild = table.GetChild(index);
if (tableChild is not AtmosMonitoringEntryContainer)
{
table.RemoveChild(tableChild);
UpdateUIEntry(data, index, table, console);
return;
}
var entryContainer = (AtmosMonitoringEntryContainer)tableChild;
entryContainer.UpdateEntry(data, data.NetEntity == _focusEntity);
}
private void UpdateConsoleTable(AtmosMonitoringConsoleComponent console, Control table, NetEntity? currTrackedEntity)
{
foreach (var tableChild in table.Children)
{
if (tableChild is not AtmosAlarmEntryContainer)
continue;
var entryContainer = (AtmosAlarmEntryContainer)tableChild;
if (entryContainer.NetEntity != currTrackedEntity)
entryContainer.RemoveAsFocus();
else if (entryContainer.NetEntity == currTrackedEntity)
entryContainer.SetAsFocus();
}
}
private void SetTrackedEntityFromNavMap(NetEntity? focusEntity)
{
if (focusEntity == null)
return;
if (!_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(_owner, out var console))
return;
foreach (var (netEnt, device) in console.AtmosDevices)
{
if (netEnt != focusEntity)
continue;
if (device.NavMapBlip != _gasPipeSensorProtoId)
return;
// Set new focus
SetFocus(focusEntity.Value, device.NetId);
// Get the scroll position of the selected entity on the selected button the UI
ActivateAutoScrollToFocus();
break;
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
AutoScrollToFocus();
}
private void ActivateAutoScrollToFocus()
{
_autoScrollActive = true;
}
private void AutoScrollToFocus()
{
if (!_autoScrollActive)
return;
var scroll = AtmosNetworksTable.Parent 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 bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
{
vScrollBar = null;
foreach (var control in scroll.Children)
{
if (control is not VScrollBar)
continue;
vScrollBar = (VScrollBar)control;
return true;
}
return false;
}
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
{
nextScrollPosition = null;
var scroll = AtmosNetworksTable.Parent 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 is not AtmosMonitoringEntryContainer)
continue;
var entry = (AtmosMonitoringEntryContainer)control;
if (entry.Data.NetEntity == _focusEntity)
return true;
nextScrollPosition += control.Height;
}
// Failed to find control
nextScrollPosition = null;
return false;
}
private void SetFocus(NetEntity focusEntity, int focusNetId)
{
_focusEntity = focusEntity;
_focusNetId = focusNetId;
NavMap.FocusNetId = focusNetId;
OnFocusChanged();
}
private void ClearFocus()
{
_focusEntity = null;
_focusNetId = null;
NavMap.FocusNetId = null;
OnFocusChanged();
}
private void OnFocusChanged()
{
UpdateNavMapBlips();
NavMap.ForceNavMapUpdate();
if (!_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(_owner, out var console))
return;
for (int index = 0; index < AtmosNetworksTable.ChildCount; index++)
{
var entry = (AtmosMonitoringEntryContainer)AtmosNetworksTable.GetChild(index);
if (entry == null)
continue;
UpdateUIEntry(entry.Data, index, AtmosNetworksTable, console);
}
}
}

View File

@@ -1,74 +0,0 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:Content.Client.Stylesheets"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Orientation="Vertical" HorizontalExpand ="True" Margin="0 0 0 3">
<!-- Network selection button -->
<Button Name="FocusButton" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 6 8" StyleClasses="OpenLeft" Access="Public">
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal" SetHeight="32">
<PanelContainer Name="NetworkColorStripe" HorizontalAlignment="Left" SetWidth="8" VerticalExpand="True" Margin="-8 -2 0 0">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#d7d7d7"/>
</PanelContainer.PanelOverride>
</PanelContainer>
<Label Name="NetworkNameLabel" Text="???" HorizontalExpand="True" HorizontalAlignment="Center"/>
</BoxContainer>
<!-- Panel that appears on selecting the device -->
<PanelContainer HorizontalExpand="True" Margin="-8 0 -14 -4" Access="Public">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252a"/>
</PanelContainer.PanelOverride>
<BoxContainer Name="MainDataContainer" HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<Control>
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
<Label Name="TemperatureHeaderLabel" Text="{Loc 'atmos-alerts-window-temperature-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
<Label Name="PressureHeaderLabel" Text="{Loc 'atmos-alerts-window-pressure-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
<Label Name="TotalMolHeaderLabel" Text="{Loc 'atmos-alerts-window-total-mol-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
</BoxContainer>
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#202023"/>
</PanelContainer.PanelOverride>
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
<Label Name="TemperatureLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
<Label Name="PressureLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
<Label Name="TotalMolLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
</BoxContainer>
</PanelContainer>
<BoxContainer HorizontalExpand="True" Orientation="Horizontal" Margin="8 0">
<TextureRect Name="ArrowTexture" VerticalAlignment="Center" SetSize="12 12" Stretch="KeepAspectCentered" Margin="3 0" TexturePath="/Textures/Interface/Nano/triangle_right.png"></TextureRect>
<Label Name="GasesHeaderLabel" Text="{Loc 'atmos-monitoring-window-label-gases'}" HorizontalAlignment="Left" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="4 0 0 0" SetHeight="24"></Label>
</BoxContainer>
</BoxContainer>
</Control>
<!-- Atmosphere status -->
<Control Name="FocusContainer" ReservesSpace="False" Visible="False">
<!-- Main container for displaying atmospheric data -->
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#202023"/>
</PanelContainer.PanelOverride>
<!-- Gas entries added via C# code -->
<GridContainer Name="GasGridContainer" HorizontalExpand="True" Columns = "4"></GridContainer>
</PanelContainer>
</BoxContainer>
</Control>
</BoxContainer>
<!-- If the alarm is inactive, this is label is displayed instead -->
<Label Name="NoDataLabel" Text="{Loc 'atmos-alerts-window-no-data-available'}" HorizontalAlignment="Center" Margin="0 15" FontColorOverride="#a9a9a9" ReservesSpace="False" Visible="False"></Label>
</PanelContainer>
</BoxContainer>
</Button>
</BoxContainer>

View File

@@ -1,166 +0,0 @@
using Content.Client.Stylesheets;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.FixedPoint;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using System.Linq;
namespace Content.Client.Atmos.Consoles;
[GenerateTypedNameReferences]
public sealed partial class AtmosMonitoringEntryContainer : BoxContainer
{
public AtmosMonitoringConsoleEntry Data;
private readonly IEntityManager _entManager;
private readonly IResourceCache _cache;
public AtmosMonitoringEntryContainer(AtmosMonitoringConsoleEntry data)
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_cache = IoCManager.Resolve<IResourceCache>();
Data = data;
// Modulate colored stripe
NetworkColorStripe.Modulate = data.Color;
// Load fonts
var headerFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), 11);
var normalFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11);
// Set fonts
TemperatureHeaderLabel.FontOverride = headerFont;
PressureHeaderLabel.FontOverride = headerFont;
TotalMolHeaderLabel.FontOverride = headerFont;
GasesHeaderLabel.FontOverride = headerFont;
TemperatureLabel.FontOverride = normalFont;
PressureLabel.FontOverride = normalFont;
TotalMolLabel.FontOverride = normalFont;
NoDataLabel.FontOverride = headerFont;
}
public void UpdateEntry(AtmosMonitoringConsoleEntry updatedData, bool isFocus)
{
// Load fonts
var normalFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11);
// Update name and values
if (!string.IsNullOrEmpty(updatedData.Address))
NetworkNameLabel.Text = Loc.GetString("atmos-alerts-window-alarm-label", ("name", updatedData.EntityName), ("address", updatedData.Address));
else
NetworkNameLabel.Text = Loc.GetString(updatedData.EntityName);
Data = updatedData;
// Modulate colored stripe
NetworkColorStripe.Modulate = Data.Color;
// Focus updates
if (isFocus)
SetAsFocus();
else
RemoveAsFocus();
// Check if powered
if (!updatedData.IsPowered)
{
MainDataContainer.Visible = false;
NoDataLabel.Visible = true;
return;
}
// Set container visibility
MainDataContainer.Visible = true;
NoDataLabel.Visible = false;
// Update temperature
var isNotVacuum = updatedData.TotalMolData > 1e-6f;
var tempK = (FixedPoint2)updatedData.TemperatureData;
var tempC = (FixedPoint2)TemperatureHelpers.KelvinToCelsius(tempK.Float());
TemperatureLabel.Text = isNotVacuum ?
Loc.GetString("atmos-alerts-window-temperature-value", ("valueInC", tempC), ("valueInK", tempK)) :
Loc.GetString("atmos-alerts-window-invalid-value");
TemperatureLabel.FontColorOverride = isNotVacuum ? Color.DarkGray : StyleNano.DisabledFore;
// Update pressure
PressureLabel.Text = Loc.GetString("atmos-alerts-window-pressure-value", ("value", (FixedPoint2)updatedData.PressureData));
PressureLabel.FontColorOverride = isNotVacuum ? Color.DarkGray : StyleNano.DisabledFore;
// Update total mol
TotalMolLabel.Text = Loc.GetString("atmos-alerts-window-total-mol-value", ("value", (FixedPoint2)updatedData.TotalMolData));
TotalMolLabel.FontColorOverride = isNotVacuum ? Color.DarkGray : StyleNano.DisabledFore;
// Update other present gases
GasGridContainer.RemoveAllChildren();
if (updatedData.GasData.Count() == 0)
{
// No gases
var gasLabel = new Label()
{
Text = Loc.GetString("atmos-alerts-window-other-gases-value-nil"),
FontOverride = normalFont,
FontColorOverride = StyleNano.DisabledFore,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
Margin = new Thickness(0, 2, 0, 0),
SetHeight = 24f,
};
GasGridContainer.AddChild(gasLabel);
}
else
{
// Add an entry for each gas
foreach (var (gas, percent) in updatedData.GasData)
{
var gasPercent = (FixedPoint2)0f;
gasPercent = percent * 100f;
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
var gasLabel = new Label()
{
Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasAbbreviation), ("value", gasPercent)),
FontOverride = normalFont,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
Margin = new Thickness(0, 2, 0, 0),
SetHeight = 24f,
};
GasGridContainer.AddChild(gasLabel);
}
}
}
public void SetAsFocus()
{
FocusButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
ArrowTexture.TexturePath = "/Textures/Interface/Nano/inverted_triangle.svg.png";
FocusContainer.Visible = true;
}
public void RemoveAsFocus()
{
FocusButton.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen);
ArrowTexture.TexturePath = "/Textures/Interface/Nano/triangle_right.png";
FocusContainer.Visible = false;
}
}

View File

@@ -1,23 +0,0 @@
using Content.Client.Atmos.UI;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Piping.Binary.Components;
namespace Content.Client.Atmos.EntitySystems;
public sealed class GasPressurePumpSystem : SharedGasPressurePumpSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPressurePumpComponent, AfterAutoHandleStateEvent>(OnPumpUpdate);
}
private void OnPumpUpdate(Entity<GasPressurePumpComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (UserInterfaceSystem.TryGetOpenUi<GasPressurePumpBoundUserInterface>(ent.Owner, GasPressurePumpUiKey.Key, out var bui))
{
bui.Update();
}
}
}

View File

@@ -1,63 +1,65 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI;
/// <summary>
/// Initializes a <see cref="GasPressurePumpWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
namespace Content.Client.Atmos.UI
{
[ViewVariables]
private const float MaxPressure = Atmospherics.MaxOutputPressure;
[ViewVariables]
private GasPressurePumpWindow? _window;
public GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
/// <summary>
/// Initializes a <see cref="GasPressurePumpWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
{
}
[ViewVariables]
private const float MaxPressure = Atmospherics.MaxOutputPressure;
protected override void Open()
{
base.Open();
[ViewVariables]
private GasPressurePumpWindow? _window;
_window = this.CreateWindow<GasPressurePumpWindow>();
public GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
_window.PumpOutputPressureChanged += OnPumpOutputPressurePressed;
Update();
}
protected override void Open()
{
base.Open();
public void Update()
{
if (_window == null)
return;
_window = this.CreateWindow<GasPressurePumpWindow>();
_window.Title = Identity.Name(Owner, EntMan);
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
_window.PumpOutputPressureChanged += OnPumpOutputPressurePressed;
}
if (!EntMan.TryGetComponent(Owner, out GasPressurePumpComponent? pump))
return;
private void OnToggleStatusButtonPressed()
{
if (_window is null) return;
SendMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
}
_window.SetPumpStatus(pump.Enabled);
_window.MaxPressure = pump.MaxTargetPressure;
_window.SetOutputPressure(pump.TargetPressure);
}
private void OnPumpOutputPressurePressed(string value)
{
var pressure = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
if (pressure > MaxPressure) pressure = MaxPressure;
private void OnToggleStatusButtonPressed()
{
if (_window is null) return;
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
}
SendMessage(new GasPressurePumpChangeOutputPressureMessage(pressure));
}
private void OnPumpOutputPressurePressed(float value)
{
SendPredictedMessage(new GasPressurePumpChangeOutputPressureMessage(value));
/// <summary>
/// Update the UI state based on server-sent info
/// </summary>
/// <param name="state"></param>
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_window == null || state is not GasPressurePumpBoundUserInterfaceState cast)
return;
_window.Title = (cast.PumpLabel);
_window.SetPumpStatus(cast.Enabled);
_window.SetOutputPressure(cast.OutputPressure);
}
}
}

View File

@@ -1,18 +1,22 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="340 110" MinSize="340 110" Title="Pressure Pump">
MinSize="200 120" Title="Pressure Pump">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-pump-ui-pump-status}" Margin="0 0 5 0"/>
<Label Text="{Loc comp-gas-pump-ui-pump-status}"/>
<Control MinSize="5 0" />
<Button Name="ToggleStatusButton"/>
<Control HorizontalExpand="True"/>
<Button HorizontalAlignment="Right" Name="SetOutputPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-rate}" Disabled="True" Margin="0 0 5 0"/>
<Button Name="SetMaxPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-max}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-pump-ui-pump-output-pressure}"/>
<FloatSpinBox HorizontalExpand="True" Name="PumpPressureOutputInput" MinSize="70 0" />
<Control MinSize="5 0" />
<LineEdit Name="PumpPressureOutputInput" MinSize="70 0" />
<Control MinSize="5 0" />
<Button Name="SetMaxPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-max}" />
<Control MinSize="5 0" />
<Control HorizontalExpand="True" />
<Button Name="SetOutputPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-rate}" HorizontalAlignment="Right" Disabled="True"/>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
</DefaultWindow>

View File

@@ -1,8 +1,14 @@
using Content.Client.UserInterface.Controls;
using System;
using System.Collections.Generic;
using System.Globalization;
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Localization;
namespace Content.Client.Atmos.UI
{
@@ -10,25 +16,12 @@ namespace Content.Client.Atmos.UI
/// Client-side UI used to control a gas pressure pump.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class GasPressurePumpWindow : FancyWindow
public sealed partial class GasPressurePumpWindow : DefaultWindow
{
public bool PumpStatus = true;
public event Action? ToggleStatusButtonPressed;
public event Action<float>? PumpOutputPressureChanged;
public float MaxPressure
{
get => _maxPressure;
set
{
_maxPressure = value;
PumpPressureOutputInput.Value = MathF.Min(value, PumpPressureOutputInput.Value);
}
}
private float _maxPressure = Atmospherics.MaxOutputPressure;
public event Action<string>? PumpOutputPressureChanged;
public GasPressurePumpWindow()
{
@@ -37,25 +30,23 @@ namespace Content.Client.Atmos.UI
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
PumpPressureOutputInput.OnValueChanged += _ => SetOutputPressureButton.Disabled = false;
PumpPressureOutputInput.OnTextChanged += _ => SetOutputPressureButton.Disabled = false;
SetOutputPressureButton.OnPressed += _ =>
{
PumpPressureOutputInput.Value = Math.Clamp(PumpPressureOutputInput.Value, 0f, _maxPressure);
PumpOutputPressureChanged?.Invoke(PumpPressureOutputInput.Value);
PumpOutputPressureChanged?.Invoke(PumpPressureOutputInput.Text ??= "");
SetOutputPressureButton.Disabled = true;
};
SetMaxPressureButton.OnPressed += _ =>
{
PumpPressureOutputInput.Value = _maxPressure;
PumpPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.CurrentCulture);
SetOutputPressureButton.Disabled = false;
};
}
public void SetOutputPressure(float pressure)
{
PumpPressureOutputInput.Value = pressure;
PumpPressureOutputInput.Text = pressure.ToString(CultureInfo.CurrentCulture);
}
public void SetPumpStatus(bool enabled)

View File

@@ -2,7 +2,6 @@ using System.Numerics;
using Content.Client.Chat.Managers;
using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.Speech;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -142,12 +141,7 @@ namespace Content.Client.Chat.UI
Modulate = Color.White;
}
var baseOffset = 0f;
if (_entityManager.TryGetComponent<SpeechComponent>(_senderEntity, out var speech))
baseOffset = speech.SpeechBubbleOffset;
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset);
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset;
var worldPos = _transformSystem.GetWorldPosition(xform) + offset;
var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale;

View File

@@ -12,7 +12,6 @@ using Robust.Shared.Utility;
using System.Linq;
using System.Numerics;
using Content.Shared.FixedPoint;
using Robust.Client.Graphics;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Chemistry.UI
@@ -91,40 +90,10 @@ namespace Content.Client.Chemistry.UI
private ReagentButton MakeReagentButton(string text, ChemMasterReagentAmount amount, ReagentId id, bool isBuffer, string styleClass)
{
var reagentTransferButton = new ReagentButton(text, amount, id, isBuffer, styleClass);
reagentTransferButton.OnPressed += args
=> OnReagentButtonPressed?.Invoke(args, reagentTransferButton);
return reagentTransferButton;
}
/// <summary>
/// Conditionally generates a set of reagent buttons based on the supplied boolean argument.
/// This was moved outside of BuildReagentRow to facilitate conditional logic, stops indentation depth getting out of hand as well.
/// </summary>
private List<ReagentButton> CreateReagentTransferButtons(ReagentId reagent, bool isBuffer, bool addReagentButtons)
{
if (!addReagentButtons)
return new List<ReagentButton>(); // Return an empty list if reagentTransferButton creation is disabled.
var buttonConfigs = new (string text, ChemMasterReagentAmount amount, string styleClass)[]
{
("1", ChemMasterReagentAmount.U1, StyleBase.ButtonOpenBoth),
("5", ChemMasterReagentAmount.U5, StyleBase.ButtonOpenBoth),
("10", ChemMasterReagentAmount.U10, StyleBase.ButtonOpenBoth),
("25", ChemMasterReagentAmount.U25, StyleBase.ButtonOpenBoth),
("50", ChemMasterReagentAmount.U50, StyleBase.ButtonOpenBoth),
("100", ChemMasterReagentAmount.U100, StyleBase.ButtonOpenBoth),
(Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, StyleBase.ButtonOpenLeft),
};
var buttons = new List<ReagentButton>();
foreach (var (text, amount, styleClass) in buttonConfigs)
{
var reagentTransferButton = MakeReagentButton(text, amount, reagent, isBuffer, styleClass);
buttons.Add(reagentTransferButton);
}
return buttons;
var button = new ReagentButton(text, amount, id, isBuffer, styleClass);
button.OnPressed += args
=> OnReagentButtonPressed?.Invoke(args, button);
return button;
}
/// <summary>
@@ -133,36 +102,25 @@ namespace Content.Client.Chemistry.UI
/// <param name="state">State data sent by the server.</param>
public void UpdateState(BoundUserInterfaceState state)
{
var castState = (ChemMasterBoundUserInterfaceState)state;
var castState = (ChemMasterBoundUserInterfaceState) state;
if (castState.UpdateLabel)
LabelLine = GenerateLabel(castState);
// Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on
UpdatePanelInfo(castState);
BufferCurrentVolume.Text = $" {castState.BufferCurrentVolume?.Int() ?? 0}u";
InputEjectButton.Disabled = castState.InputContainerInfo is null;
OutputEjectButton.Disabled = castState.OutputContainerInfo is null;
CreateBottleButton.Disabled = castState.OutputContainerInfo?.Reagents == null;
CreatePillButton.Disabled = castState.OutputContainerInfo?.Entities == null;
UpdateDosageFields(castState);
}
//assign default values for pill and bottle fields.
private void UpdateDosageFields(ChemMasterBoundUserInterfaceState castState)
{
var output = castState.OutputContainerInfo;
BufferCurrentVolume.Text = $" {castState.BufferCurrentVolume?.Int() ?? 0}u";
InputEjectButton.Disabled = castState.InputContainerInfo is null;
OutputEjectButton.Disabled = output is null;
CreateBottleButton.Disabled = output?.Reagents == null;
CreatePillButton.Disabled = output?.Entities == null;
var remainingCapacity = output is null ? 0 : (output.MaxVolume - output.CurrentVolume).Int();
var holdsReagents = output?.Reagents != null;
var pillNumberMax = holdsReagents ? 0 : remainingCapacity;
var bottleAmountMax = holdsReagents ? remainingCapacity : 0;
var bufferVolume = castState.BufferCurrentVolume?.Int() ?? 0;
PillDosage.Value = (int)Math.Min(bufferVolume, castState.PillDosageLimit);
PillTypeButtons[castState.SelectedPillType].Pressed = true;
PillNumber.IsValid = x => x >= 0 && x <= pillNumberMax;
PillDosage.IsValid = x => x > 0 && x <= castState.PillDosageLimit;
@@ -172,19 +130,8 @@ namespace Content.Client.Chemistry.UI
PillNumber.Value = pillNumberMax;
if (BottleDosage.Value > bottleAmountMax)
BottleDosage.Value = bottleAmountMax;
// Avoid division by zero
if (PillDosage.Value > 0)
{
PillNumber.Value = Math.Min(bufferVolume / PillDosage.Value, pillNumberMax);
}
else
{
PillNumber.Value = 0;
}
BottleDosage.Value = Math.Min(bottleAmountMax, bufferVolume);
}
/// <summary>
/// Generate a product label based on reagents in the buffer.
/// </summary>
@@ -231,23 +178,46 @@ namespace Content.Client.Chemistry.UI
var bufferVol = new Label
{
Text = $"{state.BufferCurrentVolume}u",
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor }
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
};
bufferHBox.AddChild(bufferVol);
// initialises rowCount to allow for striped rows
var rowCount = 0;
foreach (var (reagent, quantity) in state.BufferReagents)
{
var reagentId = reagent;
_prototypeManager.TryIndex(reagentId.Prototype, out ReagentPrototype? proto);
// Try to get the prototype for the given reagent. This gives us its name.
_prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto);
var name = proto?.LocalizedName ?? Loc.GetString("chem-master-window-unknown-reagent-text");
var reagentColor = proto?.SubstanceColor ?? default(Color);
BufferInfo.Children.Add(BuildReagentRow(reagentColor, rowCount++, name, reagentId, quantity, true, true));
if (proto != null)
{
BufferInfo.Children.Add(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label {Text = $"{name}: "},
new Label
{
Text = $"{quantity}u",
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
},
// Padding
new Control {HorizontalExpand = true},
MakeReagentButton("1", ChemMasterReagentAmount.U1, reagent, true, StyleBase.ButtonOpenRight),
MakeReagentButton("5", ChemMasterReagentAmount.U5, reagent, true, StyleBase.ButtonOpenBoth),
MakeReagentButton("10", ChemMasterReagentAmount.U10, reagent, true, StyleBase.ButtonOpenBoth),
MakeReagentButton("25", ChemMasterReagentAmount.U25, reagent, true, StyleBase.ButtonOpenBoth),
MakeReagentButton("50", ChemMasterReagentAmount.U50, reagent, true, StyleBase.ButtonOpenBoth),
MakeReagentButton("100", ChemMasterReagentAmount.U100, reagent, true, StyleBase.ButtonOpenBoth),
MakeReagentButton(Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, reagent, true, StyleBase.ButtonOpenLeft),
}
});
}
}
}
private void BuildContainerUI(Control control, ContainerInfo? info, bool addReagentButtons)
{
control.Children.Clear();
@@ -258,111 +228,104 @@ namespace Content.Client.Chemistry.UI
{
Text = Loc.GetString("chem-master-window-no-container-loaded-text")
});
return;
}
// Name of the container and its fill status (Ex: 44/100u)
control.Children.Add(new BoxContainer
else
{
Orientation = LayoutOrientation.Horizontal,
Children =
// Name of the container and its fill status (Ex: 44/100u)
control.Children.Add(new BoxContainer
{
new Label { Text = $"{info.DisplayName}: " },
new Label
Orientation = LayoutOrientation.Horizontal,
Children =
{
Text = $"{info.CurrentVolume}/{info.MaxVolume}",
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor }
}
}
});
// Initialises rowCount to allow for striped rows
var rowCount = 0;
// Handle entities if they are not null
if (info.Entities != null)
{
foreach (var (id, quantity) in info.Entities.Select(x => (x.Id, x.Quantity)))
{
control.Children.Add(BuildReagentRow(default(Color), rowCount++, id, default(ReagentId), quantity, false, addReagentButtons));
}
}
// Handle reagents if they are not null
if (info.Reagents != null)
{
foreach (var reagent in info.Reagents)
{
_prototypeManager.TryIndex(reagent.Reagent.Prototype, out ReagentPrototype? proto);
var name = proto?.LocalizedName ?? Loc.GetString("chem-master-window-unknown-reagent-text");
var reagentColor = proto?.SubstanceColor ?? default(Color);
control.Children.Add(BuildReagentRow(reagentColor, rowCount++, name, reagent.Reagent, reagent.Quantity, false, addReagentButtons));
}
}
}
/// <summary>
/// Take reagent/entity data and present rows, labels, and buttons appropriately. todo sprites?
/// </summary>
private Control BuildReagentRow(Color reagentColor, int rowCount, string name, ReagentId reagent, FixedPoint2 quantity, bool isBuffer, bool addReagentButtons)
{
//Colors rows and sets fallback for reagentcolor to the same as background, this will hide colorPanel for entities hopefully
var rowColor1 = Color.FromHex("#1B1B1E");
var rowColor2 = Color.FromHex("#202025");
var currentRowColor = (rowCount % 2 == 1) ? rowColor1 : rowColor2;
if ((reagentColor == default(Color))|(!addReagentButtons))
{
reagentColor = currentRowColor;
}
//this calls the separated button builder, and stores the return to render after labels
var reagentButtonConstructors = CreateReagentTransferButtons(reagent, isBuffer, addReagentButtons);
// Create the row layout with the color panel
var rowContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label { Text = $"{name}: " },
new Label
{
Text = $"{quantity}u",
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor }
},
// Padding
new Control { HorizontalExpand = true },
// Colored panels for reagents
new PanelContainer
{
Name = "colorPanel",
VerticalExpand = true,
MinWidth = 4,
PanelOverride = new StyleBoxFlat
new Label {Text = $"{info.DisplayName}: "},
new Label
{
BackgroundColor = reagentColor
},
Margin = new Thickness(0, 1)
Text = $"{info.CurrentVolume}/{info.MaxVolume}",
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
}
}
}
};
});
IEnumerable<(string Name, ReagentId Id, FixedPoint2 Quantity)> contents;
if (info.Entities != null)
{
contents = info.Entities.Select(x => (x.Id, default(ReagentId), x.Quantity));
}
else if (info.Reagents != null)
{
contents = info.Reagents.Select(x =>
{
_prototypeManager.TryIndex(x.Reagent.Prototype, out ReagentPrototype? proto);
var name = proto?.LocalizedName
?? Loc.GetString("chem-master-window-unknown-reagent-text");
return (name, Id: x.Reagent, x.Quantity);
})
.OrderBy(r => r.Item1);
}
else
{
return;
}
foreach (var (name, id, quantity) in contents)
{
var inner = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label { Text = $"{name}: " },
new Label
{
Text = $"{quantity}u",
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor },
}
}
};
if (addReagentButtons)
{
var cs = inner.Children;
// Padding
cs.Add(new Control { HorizontalExpand = true });
cs.Add(MakeReagentButton(
"1", ChemMasterReagentAmount.U1, id, false, StyleBase.ButtonOpenRight));
cs.Add(MakeReagentButton(
"5", ChemMasterReagentAmount.U5, id, false, StyleBase.ButtonOpenBoth));
cs.Add(MakeReagentButton(
"10", ChemMasterReagentAmount.U10, id, false, StyleBase.ButtonOpenBoth));
cs.Add(MakeReagentButton(
"25", ChemMasterReagentAmount.U25, id, false, StyleBase.ButtonOpenBoth));
cs.Add(MakeReagentButton(
"50", ChemMasterReagentAmount.U50, id, false, StyleBase.ButtonOpenBoth));
cs.Add(MakeReagentButton(
"100", ChemMasterReagentAmount.U100, id, false, StyleBase.ButtonOpenBoth));
cs.Add(MakeReagentButton(
Loc.GetString("chem-master-window-buffer-all-amount"),
ChemMasterReagentAmount.All, id, false, StyleBase.ButtonOpenLeft));
}
control.Children.Add(inner);
}
// Add the reagent buttons after the color panel
foreach (var reagentTransferButton in reagentButtonConstructors)
{
rowContainer.AddChild(reagentTransferButton);
}
//Apply panencontainer to allow for striped rows
return new PanelContainer
{
PanelOverride = new StyleBoxFlat(currentRowColor),
Children = { rowContainer }
};
}
public string LabelLine
public String LabelLine
{
get => LabelLineEdit.Text;
set => LabelLineEdit.Text = value;
get
{
return LabelLineEdit.Text;
}
set
{
LabelLineEdit.Text = value;
}
}
}

View File

@@ -1,62 +1,17 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'comms-console-menu-title'}"
MinSize="400 300">
MinSize="400 225">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="5">
<TextEdit Name="MessageInput" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 0 5" MinHeight="100" />
<Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" ToolTip="{Loc 'comms-console-menu-announcement-button-tooltip'}" StyleClasses="OpenLeft" Access="Public" />
<Button Name="BroadcastButton" Text="{Loc 'comms-console-menu-broadcast-button'}" ToolTip="{Loc 'comms-console-menu-broadcast-button-tooltip'}" StyleClasses="OpenLeft" Access="Public" />
<!-- Main Container -->
<BoxContainer Orientation="Vertical"
HorizontalExpand="False"
VerticalExpand="True"
Margin="6 6 6 5">
<OptionButton Name="AlertLevelButton" ToolTip="{Loc 'comms-console-menu-alert-level-button-tooltip'}" StyleClasses="OpenRight" Access="Public" />
<TextEdit Name="MessageInput"
VerticalExpand="True"
HorizontalExpand="True"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MinHeight="100"/>
<Control MinSize="10 10" />
<!-- ButtonsPart -->
<BoxContainer Orientation="Vertical"
VerticalAlignment="Bottom"
SeparationOverride="4">
<!-- AnnouncePart -->
<BoxContainer Orientation="Vertical"
Margin="0 2">
<Button Name="AnnounceButton"
Access="Public"
Text="{Loc 'comms-console-menu-announcement-button'}"
ToolTip="{Loc 'comms-console-menu-announcement-button-tooltip'}"
StyleClasses="OpenLeft"
Margin="0 0 1 0"
Disabled="True"/>
<Button Name="BroadcastButton"
Access="Public"
Text="{Loc 'comms-console-menu-broadcast-button'}"
ToolTip="{Loc 'comms-console-menu-broadcast-button-tooltip'}"
StyleClasses="OpenBoth"/>
<OptionButton Name="AlertLevelButton"
Access="Public"
ToolTip="{Loc 'comms-console-menu-alert-level-button-tooltip'}"
StyleClasses="OpenRight"/>
</BoxContainer>
<!-- EmergencyPart -->
<BoxContainer Orientation="Vertical"
SeparationOverride="6">
<RichTextLabel Name="CountdownLabel"/>
<Button Name="EmergencyShuttleButton"
Access="Public"
Text="Placeholder Text"
ToolTip="{Loc 'comms-console-menu-emergency-shuttle-button-tooltip'}"/>
</BoxContainer>
</BoxContainer>
<RichTextLabel Name="CountdownLabel" VerticalExpand="True" />
<Button Name="EmergencyShuttleButton" Text="Placeholder Text" ToolTip="{Loc 'comms-console-menu-emergency-shuttle-button-tooltip'}" Access="Public" />
</BoxContainer>
</controls:FancyWindow>

View File

@@ -12,7 +12,6 @@ using Content.Client.IoC;
using Content.Client.Launcher;
using Content.Client.Lobby;
using Content.Client.MainMenu;
using Content.Client.Overlays;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Radiation.Overlays;
@@ -160,7 +159,6 @@ namespace Content.Client.Entry
_parallaxManager.LoadDefaultParallax();
_overlayManager.AddOverlay(new CP14BasePostProcessOverlay()); // CP14-PostProcess
_overlayManager.AddOverlay(new SingularityOverlay());
_overlayManager.AddOverlay(new RadiationPulseOverlay());
_chatManager.Initialize();

View File

@@ -1,8 +0,0 @@
using Content.Shared.Explosion.EntitySystems;
namespace Content.Client.Explosion;
public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
{
}

View File

@@ -1,101 +0,0 @@
using Content.Shared.Holopad;
using Content.Shared.Silicons.StationAi;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.Player;
using System.Numerics;
namespace Content.Client.Holopad;
public sealed class HolopadBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
[ViewVariables]
private HolopadWindow? _window;
public HolopadBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_window = this.CreateWindow<HolopadWindow>();
_window.Title = Loc.GetString("holopad-window-title", ("title", EntMan.GetComponent<MetaDataComponent>(Owner).EntityName));
if (this.UiKey is not HolopadUiKey)
{
Close();
return;
}
var uiKey = (HolopadUiKey)this.UiKey;
// AIs will see a different holopad interface to crew when interacting with them in the world
if (uiKey == HolopadUiKey.InteractionWindow && EntMan.HasComponent<StationAiHeldComponent>(_playerManager.LocalEntity))
uiKey = HolopadUiKey.InteractionWindowForAi;
_window.SetState(Owner, uiKey);
_window.UpdateState(new Dictionary<NetEntity, string>());
// Set message actions
_window.SendHolopadStartNewCallMessageAction += SendHolopadStartNewCallMessage;
_window.SendHolopadAnswerCallMessageAction += SendHolopadAnswerCallMessage;
_window.SendHolopadEndCallMessageAction += SendHolopadEndCallMessage;
_window.SendHolopadStartBroadcastMessageAction += SendHolopadStartBroadcastMessage;
_window.SendHolopadActivateProjectorMessageAction += SendHolopadActivateProjectorMessage;
_window.SendHolopadRequestStationAiMessageAction += SendHolopadRequestStationAiMessage;
// If this call is addressed to an AI, open the window in the bottom right hand corner of the screen
if (uiKey == HolopadUiKey.AiRequestWindow)
_window.OpenCenteredAt(new Vector2(1f, 1f));
// Otherwise offset to the left so the holopad can still be seen
else
_window.OpenCenteredAt(new Vector2(0.3333f, 0.50f));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
var castState = (HolopadBoundInterfaceState)state;
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
_window?.UpdateState(castState.Holopads);
}
public void SendHolopadStartNewCallMessage(NetEntity receiver)
{
SendMessage(new HolopadStartNewCallMessage(receiver));
}
public void SendHolopadAnswerCallMessage()
{
SendMessage(new HolopadAnswerCallMessage());
}
public void SendHolopadEndCallMessage()
{
SendMessage(new HolopadEndCallMessage());
}
public void SendHolopadStartBroadcastMessage()
{
SendMessage(new HolopadStartBroadcastMessage());
}
public void SendHolopadActivateProjectorMessage()
{
SendMessage(new HolopadActivateProjectorMessage());
}
public void SendHolopadRequestStationAiMessage()
{
SendMessage(new HolopadStationAiRequestMessage());
}
}

View File

@@ -1,172 +0,0 @@
using Content.Shared.Chat.TypingIndicator;
using Content.Shared.Holopad;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using System.Linq;
namespace Content.Client.Holopad;
public sealed class HolopadSystem : SharedHolopadSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HolopadHologramComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<HolopadHologramComponent, BeforePostShaderRenderEvent>(OnShaderRender);
SubscribeAllEvent<TypingChangedEvent>(OnTypingChanged);
SubscribeNetworkEvent<PlayerSpriteStateRequest>(OnPlayerSpriteStateRequest);
SubscribeNetworkEvent<PlayerSpriteStateMessage>(OnPlayerSpriteStateMessage);
}
private void OnComponentInit(EntityUid uid, HolopadHologramComponent component, ComponentInit ev)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
UpdateHologramSprite(uid);
}
private void OnShaderRender(EntityUid uid, HolopadHologramComponent component, BeforePostShaderRenderEvent ev)
{
if (ev.Sprite.PostShader == null)
return;
ev.Sprite.PostShader.SetParameter("t", (float)_timing.CurTime.TotalSeconds * component.ScrollRate);
}
private void OnTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs args)
{
var uid = args.SenderSession.AttachedEntity;
if (!Exists(uid))
return;
if (!HasComp<HolopadUserComponent>(uid))
return;
var netEv = new HolopadUserTypingChangedEvent(GetNetEntity(uid.Value), ev.IsTyping);
RaiseNetworkEvent(netEv);
}
private void OnPlayerSpriteStateRequest(PlayerSpriteStateRequest ev)
{
var targetPlayer = GetEntity(ev.TargetPlayer);
var player = _playerManager.LocalSession?.AttachedEntity;
// Ignore the request if received by a player who isn't the target
if (targetPlayer != player)
return;
if (!TryComp<SpriteComponent>(player, out var playerSprite))
return;
var spriteLayerData = new List<PrototypeLayerData>();
if (playerSprite.Visible)
{
// Record the RSI paths, state names and shader paramaters of all visible layers
for (int i = 0; i < playerSprite.AllLayers.Count(); i++)
{
if (!playerSprite.TryGetLayer(i, out var layer))
continue;
if (!layer.Visible ||
string.IsNullOrEmpty(layer.ActualRsi?.Path.ToString()) ||
string.IsNullOrEmpty(layer.State.Name))
continue;
var layerDatum = new PrototypeLayerData();
layerDatum.RsiPath = layer.ActualRsi.Path.ToString();
layerDatum.State = layer.State.Name;
if (layer.CopyToShaderParameters != null)
{
var key = (string)layer.CopyToShaderParameters.LayerKey;
if (playerSprite.LayerMapTryGet(key, out var otherLayerIdx) &&
playerSprite.TryGetLayer(otherLayerIdx, out var otherLayer) &&
otherLayer.Visible)
{
layerDatum.MapKeys = new() { key };
layerDatum.CopyToShaderParameters = new PrototypeCopyToShaderParameters()
{
LayerKey = key,
ParameterTexture = layer.CopyToShaderParameters.ParameterTexture,
ParameterUV = layer.CopyToShaderParameters.ParameterUV
};
}
}
spriteLayerData.Add(layerDatum);
}
}
// Return the recorded data to the server
var evResponse = new PlayerSpriteStateMessage(ev.TargetPlayer, spriteLayerData.ToArray());
RaiseNetworkEvent(evResponse);
}
private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev)
{
UpdateHologramSprite(GetEntity(ev.SpriteEntity), ev.SpriteLayerData);
}
private void UpdateHologramSprite(EntityUid uid, PrototypeLayerData[]? layerData = null)
{
if (!TryComp<SpriteComponent>(uid, out var hologramSprite))
return;
if (!TryComp<HolopadHologramComponent>(uid, out var holopadhologram))
return;
for (int i = hologramSprite.AllLayers.Count() - 1; i >= 0; i--)
hologramSprite.RemoveLayer(i);
if (layerData == null || layerData.Length == 0)
{
layerData = new PrototypeLayerData[1];
layerData[0] = new PrototypeLayerData()
{
RsiPath = holopadhologram.RsiPath,
State = holopadhologram.RsiState
};
}
for (int i = 0; i < layerData.Length; i++)
{
var layer = layerData[i];
layer.Shader = "unshaded";
hologramSprite.AddLayer(layerData[i], i);
}
UpdateHologramShader(uid, hologramSprite, holopadhologram);
}
private void UpdateHologramShader(EntityUid uid, SpriteComponent sprite, HolopadHologramComponent holopadHologram)
{
// Find the texture height of the largest layer
float texHeight = sprite.AllLayers.Max(x => x.PixelSize.Y);
var instance = _prototypeManager.Index<ShaderPrototype>(holopadHologram.ShaderName).InstanceUnique();
instance.SetParameter("color1", new Vector3(holopadHologram.Color1.R, holopadHologram.Color1.G, holopadHologram.Color1.B));
instance.SetParameter("color2", new Vector3(holopadHologram.Color2.R, holopadHologram.Color2.G, holopadHologram.Color2.B));
instance.SetParameter("alpha", holopadHologram.Alpha);
instance.SetParameter("intensity", holopadHologram.Intensity);
instance.SetParameter("texHeight", texHeight);
instance.SetParameter("t", (float)_timing.CurTime.TotalSeconds * holopadHologram.ScrollRate);
sprite.PostShader = instance;
sprite.RaiseShaderEvent = true;
}
}

View File

@@ -1,118 +0,0 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Resizable="False"
MaxSize="400 800"
MinSize="400 150">
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<BoxContainer Name="ControlsLockOutContainer" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" ReservesSpace="False" Visible="False">
<!-- Header text -->
<controls:StripeBack>
<PanelContainer>
<RichTextLabel Name="EmergencyBroadcastText" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="10 10 10 10" ReservesSpace="False"/>
</PanelContainer>
</controls:StripeBack>
<Label Text="{Loc 'holopad-window-controls-locked-out'}" HorizontalAlignment="Center" Margin="10 5 10 0" ReservesSpace="False"/>
<RichTextLabel Name="LockOutIdText" HorizontalAlignment="Center" Margin="10 5 10 0" ReservesSpace="False"/>
<Label Name="LockOutCountDownText" Text="{Loc 'holopad-window-controls-unlock-countdown'}" HorizontalAlignment="Center" Margin="10 15 10 10" ReservesSpace="False"/>
</BoxContainer>
<BoxContainer Name="ControlsContainer" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" ReservesSpace="False">
<!-- Active call controls (either this or the call placement controls will be active) -->
<BoxContainer Name="ActiveCallControlsContainer" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" ReservesSpace="False">
<!-- Header text -->
<BoxContainer MinHeight="60" Orientation="Vertical" VerticalAlignment="Center">
<Label Name="CallStatusText" Margin="10 5 10 0" ReservesSpace="False"/>
<BoxContainer Name="CallerIdContainer" Orientation="Vertical" ReservesSpace="False">
<RichTextLabel Name="CallerIdText" HorizontalAlignment="Center" Margin="0 0 0 0"/>
<Label Text="{Loc 'holopad-window-relay-label'}" Margin="10 5 10 0" ReservesSpace="False"/>
<RichTextLabel Name="HolopadIdText" HorizontalAlignment="Center" Margin="0 0 0 10"/>
</BoxContainer>
</BoxContainer>
<!-- Controls (the answer call button is absent when the phone is not ringing) -->
<GridContainer Columns="2" ReservesSpace="False">
<Control HorizontalExpand="True" Margin="10 0 2 5">
<Button Name="AnswerCallButton" Text="{Loc 'holopad-window-answer-call'}" StyleClasses="OpenRight" Margin="0 0 0 5" Disabled="True"/>
</Control>
<Control HorizontalExpand="True" Margin="2 0 10 5">
<Button Name="EndCallButton" Text="{Loc 'holopad-window-end-call'}" StyleClasses="OpenLeft" Margin="0 0 0 5" Disabled="True"/>
</Control>
</GridContainer>
</BoxContainer>
<!-- Call placement controls (either this or the active call controls will be active) -->
<BoxContainer Name="CallPlacementControlsContainer" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" ReservesSpace="False">
<controls:StripeBack>
<PanelContainer>
<BoxContainer Orientation="Vertical">
<RichTextLabel Name="SubtitleText" HorizontalAlignment="Center" Margin="0 5 0 0"/>
<RichTextLabel Name="OptionsText" HorizontalAlignment="Center" Margin="0 0 0 5"/>
</BoxContainer>
</PanelContainer>
</controls:StripeBack>
<!-- Request the station AI or activate the holopad projector (only one of these should be active at a time) -->
<BoxContainer Name="RequestStationAiContainer" Orientation="Vertical" ReservesSpace="False" Visible="False">
<Button Name="RequestStationAiButton" Text="{Loc 'holopad-window-request-station-ai'}" Margin="10 5 10 5" Disabled="False"/>
</BoxContainer>
<BoxContainer Name="ActivateProjectorContainer" Orientation="Vertical" ReservesSpace="False" Visible="False">
<Button Name="ActivateProjectorButton" Text="{Loc 'holopad-window-activate-projector'}" Margin="10 5 10 5" Disabled="False"/>
</BoxContainer>
<!-- List of contactable holopads (the list is created in C#) -->
<BoxContainer Name="HolopadContactListContainer" Orientation="Vertical" Margin="10 0 10 5" ReservesSpace="False" Visible="False">
<PanelContainer Name="HolopadContactListHeaderPanel">
<Label Text="{Loc 'holopad-window-select-contact-from-list'}" HorizontalAlignment="Center" Margin="0 3 0 3"/>
</PanelContainer>
<PanelContainer Name="HolopadContactListPanel">
<BoxContainer Orientation="Vertical">
<!-- Contact filter -->
<LineEdit Name="SearchLineEdit" HorizontalExpand="True" Margin="4, 4, 4, 0"
PlaceHolder="{Loc holopad-window-filter-line-placeholder}" />
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" Margin="8, 8, 8, 8" MinHeight="256">
<!-- If there is no data yet, this will be displayed -->
<BoxContainer Name="FetchingAvailableHolopadsContainer" HorizontalAlignment="Center" HorizontalExpand="True" VerticalExpand="True" ReservesSpace="False">
<Label Text="{Loc 'holopad-window-fetching-contacts-list'}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</BoxContainer>
<!-- Container for the contacts -->
<BoxContainer Name="ContactsList" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 0"/>
</ScrollContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
<!-- Button to start an emergency broadcast (the user requires a certain level of access to interact with it) -->
<BoxContainer Name="StartBroadcastContainer" Orientation="Vertical" ReservesSpace="False" Visible="False">
<Button Name="StartBroadcastButton" Text="{Loc 'holopad-window-emergency-broadcast'}" Margin="10 0 10 5" Disabled="False" ReservesSpace="False"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<!-- Footer -->
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'holopad-window-flavor-left'}" StyleClasses="WindowFooterText" />
<Label Text="{Loc 'holopad-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>

View File

@@ -1,344 +0,0 @@
using Content.Client.Popups;
using Content.Client.UserInterface.Controls;
using Content.Shared.Access.Systems;
using Content.Shared.Holopad;
using Content.Shared.Telephone;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Linq;
namespace Content.Client.Holopad;
[GenerateTypedNameReferences]
public sealed partial class HolopadWindow : FancyWindow
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly SharedHolopadSystem _holopadSystem = default!;
private readonly SharedTelephoneSystem _telephoneSystem = default!;
private readonly AccessReaderSystem _accessReaderSystem = default!;
private readonly PopupSystem _popupSystem = default!;
private EntityUid? _owner = null;
private HolopadUiKey _currentUiKey;
private TelephoneState _currentState;
private TelephoneState _previousState;
private TimeSpan _buttonUnlockTime;
private float _updateTimer = 0.25f;
private const float UpdateTime = 0.25f;
private TimeSpan _buttonUnlockDelay = TimeSpan.FromSeconds(0.5f);
public event Action<NetEntity>? SendHolopadStartNewCallMessageAction;
public event Action? SendHolopadAnswerCallMessageAction;
public event Action? SendHolopadEndCallMessageAction;
public event Action? SendHolopadStartBroadcastMessageAction;
public event Action? SendHolopadActivateProjectorMessageAction;
public event Action? SendHolopadRequestStationAiMessageAction;
public HolopadWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_holopadSystem = _entManager.System<SharedHolopadSystem>();
_telephoneSystem = _entManager.System<SharedTelephoneSystem>();
_accessReaderSystem = _entManager.System<AccessReaderSystem>();
_popupSystem = _entManager.System<PopupSystem>();
_buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay;
// Assign button actions
AnswerCallButton.OnPressed += args => { OnHolopadAnswerCallMessage(); };
EndCallButton.OnPressed += args => { OnHolopadEndCallMessage(); };
StartBroadcastButton.OnPressed += args => { OnHolopadStartBroadcastMessage(); };
ActivateProjectorButton.OnPressed += args => { OnHolopadActivateProjectorMessage(); };
RequestStationAiButton.OnPressed += args => { OnHolopadRequestStationAiMessage(); };
// XML formatting
AnswerCallButton.AddStyleClass("ButtonAccept");
EndCallButton.AddStyleClass("Caution");
StartBroadcastButton.AddStyleClass("Caution");
HolopadContactListPanel.PanelOverride = new StyleBoxFlat
{
BackgroundColor = new Color(47, 47, 59) * Color.DarkGray,
BorderColor = new Color(82, 82, 82), //new Color(70, 73, 102),
BorderThickness = new Thickness(2),
};
HolopadContactListHeaderPanel.PanelOverride = new StyleBoxFlat
{
BackgroundColor = new Color(82, 82, 82),
};
EmergencyBroadcastText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-emergency-broadcast-in-progress")));
SubtitleText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-subtitle")));
OptionsText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-options")));
}
#region: Button actions
private void OnSendHolopadStartNewCallMessage(NetEntity receiver)
{
SendHolopadStartNewCallMessageAction?.Invoke(receiver);
}
private void OnHolopadAnswerCallMessage()
{
SendHolopadAnswerCallMessageAction?.Invoke();
}
private void OnHolopadEndCallMessage()
{
SendHolopadEndCallMessageAction?.Invoke();
if (_currentUiKey == HolopadUiKey.AiRequestWindow)
Close();
}
private void OnHolopadStartBroadcastMessage()
{
if (_playerManager.LocalSession?.AttachedEntity == null || _owner == null)
return;
var player = _playerManager.LocalSession.AttachedEntity;
if (!_accessReaderSystem.IsAllowed(player.Value, _owner.Value))
{
_popupSystem.PopupClient(Loc.GetString("holopad-window-access-denied"), _owner.Value, player.Value);
return;
}
SendHolopadStartBroadcastMessageAction?.Invoke();
}
private void OnHolopadActivateProjectorMessage()
{
SendHolopadActivateProjectorMessageAction?.Invoke();
}
private void OnHolopadRequestStationAiMessage()
{
SendHolopadRequestStationAiMessageAction?.Invoke();
}
#endregion
public void SetState(EntityUid owner, HolopadUiKey uiKey)
{
_owner = owner;
_currentUiKey = uiKey;
// Determines what UI containers are available to the user.
// Components of these will be toggled on and off when
// UpdateAppearance() is called
switch (uiKey)
{
case HolopadUiKey.InteractionWindow:
RequestStationAiContainer.Visible = true;
HolopadContactListContainer.Visible = true;
StartBroadcastContainer.Visible = true;
break;
case HolopadUiKey.InteractionWindowForAi:
ActivateProjectorContainer.Visible = true;
StartBroadcastContainer.Visible = true;
break;
case HolopadUiKey.AiActionWindow:
HolopadContactListContainer.Visible = true;
StartBroadcastContainer.Visible = true;
break;
case HolopadUiKey.AiRequestWindow:
break;
}
}
public void UpdateState(Dictionary<NetEntity, string> holopads)
{
if (_owner == null || !_entManager.TryGetComponent<TelephoneComponent>(_owner.Value, out var telephone))
return;
// Caller ID text
var callerId = _telephoneSystem.GetFormattedCallerIdForEntity(telephone.LastCallerId.Item1, telephone.LastCallerId.Item2, Color.LightGray, "Default", 11);
var holoapdId = _telephoneSystem.GetFormattedDeviceIdForEntity(telephone.LastCallerId.Item3, Color.LightGray, "Default", 11);
CallerIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
HolopadIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(holoapdId));
LockOutIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
// Sort holopads alphabetically
var holopadArray = holopads.ToArray();
Array.Sort(holopadArray, AlphabeticalSort);
// Clear excess children from the contact list
while (ContactsList.ChildCount > holopadArray.Length)
ContactsList.RemoveChild(ContactsList.GetChild(ContactsList.ChildCount - 1));
// Make / update required children
for (int i = 0; i < holopadArray.Length; i++)
{
var (netEntity, label) = holopadArray[i];
if (i >= ContactsList.ChildCount)
{
var newContactButton = new HolopadContactButton();
newContactButton.OnPressed += args => { OnSendHolopadStartNewCallMessage(newContactButton.NetEntity); };
ContactsList.AddChild(newContactButton);
}
var child = ContactsList.GetChild(i);
if (child is not HolopadContactButton)
continue;
var contactButton = (HolopadContactButton)child;
contactButton.UpdateValues(netEntity, label);
}
// Update buttons
UpdateAppearance();
}
private void UpdateAppearance()
{
if (_owner == null || !_entManager.TryGetComponent<TelephoneComponent>(_owner.Value, out var telephone))
return;
if (_owner == null || !_entManager.TryGetComponent<HolopadComponent>(_owner.Value, out var holopad))
return;
var hasBroadcastAccess = !_holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad));
var localPlayer = _playerManager.LocalSession?.AttachedEntity;
ControlsLockOutContainer.Visible = _holopadSystem.IsHolopadControlLocked((_owner.Value, holopad), localPlayer);
ControlsContainer.Visible = !ControlsLockOutContainer.Visible;
// Temporarily disable the interface buttons when the call state changes to prevent any misclicks
if (_currentState != telephone.CurrentState)
{
_previousState = _currentState;
_currentState = telephone.CurrentState;
_buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay;
}
var lockButtons = _timing.CurTime < _buttonUnlockTime;
// Make / update required children
foreach (var child in ContactsList.Children)
{
if (child is not HolopadContactButton contactButton)
continue;
var passesFilter = string.IsNullOrEmpty(SearchLineEdit.Text) ||
contactButton.Text?.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase) == true;
contactButton.Visible = passesFilter;
contactButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
}
// Update control text
var cooldown = _holopadSystem.GetHolopadBroadcastCoolDown((_owner.Value, holopad));
var cooldownString = $"{cooldown.Minutes:00}:{cooldown.Seconds:00}";
StartBroadcastButton.Text = _holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad)) ?
Loc.GetString("holopad-window-emergency-broadcast-with-countdown", ("countdown", cooldownString)) :
Loc.GetString("holopad-window-emergency-broadcast");
var lockout = _holopadSystem.GetHolopadControlLockedPeriod((_owner.Value, holopad));
var lockoutString = $"{lockout.Minutes:00}:{lockout.Seconds:00}";
LockOutCountDownText.Text = Loc.GetString("holopad-window-controls-unlock-countdown", ("countdown", lockoutString));
switch (_currentState)
{
case TelephoneState.Idle:
CallStatusText.Text = Loc.GetString("holopad-window-no-calls-in-progress"); break;
case TelephoneState.Calling:
CallStatusText.Text = Loc.GetString("holopad-window-outgoing-call"); break;
case TelephoneState.Ringing:
CallStatusText.Text = (_currentUiKey == HolopadUiKey.AiRequestWindow) ?
Loc.GetString("holopad-window-ai-request") : Loc.GetString("holopad-window-incoming-call"); break;
case TelephoneState.InCall:
CallStatusText.Text = Loc.GetString("holopad-window-call-in-progress"); break;
case TelephoneState.EndingCall:
if (_previousState == TelephoneState.Calling || _previousState == TelephoneState.Idle)
CallStatusText.Text = Loc.GetString("holopad-window-call-rejected");
else
CallStatusText.Text = Loc.GetString("holopad-window-call-ending");
break;
}
// Update control disability
AnswerCallButton.Disabled = (_currentState != TelephoneState.Ringing || lockButtons);
EndCallButton.Disabled = (_currentState == TelephoneState.Idle || _currentState == TelephoneState.EndingCall || lockButtons);
StartBroadcastButton.Disabled = (_currentState != TelephoneState.Idle || !hasBroadcastAccess || lockButtons);
RequestStationAiButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
ActivateProjectorButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
// Update control visibility
FetchingAvailableHolopadsContainer.Visible = (ContactsList.ChildCount == 0);
ActiveCallControlsContainer.Visible = (_currentState != TelephoneState.Idle || _currentUiKey == HolopadUiKey.AiRequestWindow);
CallPlacementControlsContainer.Visible = !ActiveCallControlsContainer.Visible;
CallerIdContainer.Visible = (_currentState == TelephoneState.Ringing);
AnswerCallButton.Visible = (_currentState == TelephoneState.Ringing);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
_updateTimer += args.DeltaSeconds;
if (_updateTimer >= UpdateTime)
{
_updateTimer -= UpdateTime;
UpdateAppearance();
}
}
private sealed class HolopadContactButton : Button
{
public NetEntity NetEntity;
public HolopadContactButton()
{
HorizontalExpand = true;
SetHeight = 32;
Margin = new Thickness(0f, 1f, 0f, 1f);
ReservesSpace = false;
}
public void UpdateValues(NetEntity netEntity, string label)
{
NetEntity = netEntity;
Text = Loc.GetString("holopad-window-contact-label", ("label", label));
}
}
private int AlphabeticalSort(KeyValuePair<NetEntity, string> x, KeyValuePair<NetEntity, string> y)
{
if (string.IsNullOrEmpty(x.Value))
return -1;
if (string.IsNullOrEmpty(y.Value))
return 1;
return x.Value.CompareTo(y.Value);
}
}

View File

@@ -455,21 +455,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
{
EntityUid dummyEnt;
EntProtoId? previewEntity = null;
if (humanoid != null && jobClothes)
{
job ??= GetPreferredJob(humanoid);
previewEntity = job.JobPreviewEntity ?? (EntProtoId?)job?.JobEntity;
}
if (previewEntity != null)
{
// Special type like borg or AI, do not spawn a human just spawn the entity.
dummyEnt = EntityManager.SpawnEntity(previewEntity, MapCoordinates.Nullspace);
return dummyEnt;
}
else if (humanoid is not null)
if (humanoid is not null)
{
var dummy = _prototypeManager.Index<SpeciesPrototype>(humanoid.Species).DollPrototype;
dummyEnt = EntityManager.SpawnEntity(dummy, MapCoordinates.Nullspace);
@@ -483,8 +469,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
if (humanoid != null && jobClothes)
{
DebugTools.Assert(job != null);
job ??= GetPreferredJob(humanoid);
GiveDummyJobClothes(dummyEnt, humanoid, job);
if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))

View File

@@ -6,7 +6,6 @@
SeparationOverride="0"
Name="InternalHBox">
<SpriteView Scale="2 2"
Margin="0 4 4 4"
OverrideDirection="South"
Name="View"/>
<Label Name="DescriptionLabel"

View File

@@ -140,7 +140,7 @@
</BoxContainer>
<!-- Right side -->
<BoxContainer Orientation="Vertical" VerticalExpand="True" VerticalAlignment="Center">
<SpriteView Name="SpriteView" Scale="8 8" Margin="4" SizeFlagsStretchRatio="1" />
<SpriteView Name="SpriteView" Scale="8 8" SizeFlagsStretchRatio="1" />
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 5">
<Button Name="SpriteRotateLeft" Text="◀" StyleClasses="OpenRight" />
<cc:VSeparator Margin="2 0 3 0" />

View File

@@ -1,46 +0,0 @@
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems;
/// <summary>
/// Controls the switching of motion and standing still animation
/// </summary>
public sealed class ClientSpriteMovementSystem : SharedSpriteMovementSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
private EntityQuery<SpriteComponent> _spriteQuery;
public override void Initialize()
{
base.Initialize();
_spriteQuery = GetEntityQuery<SpriteComponent>();
SubscribeLocalEvent<SpriteMovementComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(Entity<SpriteMovementComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (!_spriteQuery.TryGetComponent(ent, out var sprite))
return;
if (ent.Comp.IsMoving)
{
foreach (var (layer, state) in ent.Comp.MovementLayers)
{
sprite.LayerSetData(layer, state);
}
}
else
{
foreach (var (layer, state) in ent.Comp.NoMovementLayers)
{
sprite.LayerSetData(layer, state);
}
}
}
}

View File

@@ -0,0 +1,51 @@
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems;
/// <summary>
/// Handles setting sprite states based on whether an entity has movement input.
/// </summary>
public sealed class SpriteMovementSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
private EntityQuery<SpriteComponent> _spriteQuery;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpriteMovementComponent, MoveInputEvent>(OnSpriteMoveInput);
_spriteQuery = GetEntityQuery<SpriteComponent>();
}
private void OnSpriteMoveInput(EntityUid uid, SpriteMovementComponent component, ref MoveInputEvent args)
{
if (!_timing.IsFirstTimePredicted)
return;
var oldMoving = (SharedMoverController.GetNormalizedMovement(args.OldMovement) & MoveButtons.AnyDirection) != MoveButtons.None;
var moving = (SharedMoverController.GetNormalizedMovement(args.Entity.Comp.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None;
if (oldMoving == moving || !_spriteQuery.TryGetComponent(uid, out var sprite))
return;
if (moving)
{
foreach (var (layer, state) in component.MovementLayers)
{
sprite.LayerSetData(layer, state);
}
}
else
{
foreach (var (layer, state) in component.NoMovementLayers)
{
sprite.LayerSetData(layer, state);
}
}
}
}

View File

@@ -9,7 +9,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
using Robust.Client.UserInterface.RichText;
using Content.Client.UserInterface.RichText;
using Robust.Shared.Input;
namespace Content.Client.Paper.UI
@@ -44,8 +43,7 @@ namespace Content.Client.Paper.UI
typeof(BulletTag),
typeof(ColorTag),
typeof(HeadingTag),
typeof(ItalicTag),
typeof(MonoTag)
typeof(ItalicTag)
};
public event Action<string>? OnSaved;

View File

@@ -385,6 +385,26 @@ public partial class NavMapControl : MapGridControl
if (PostWallDrawingAction != null)
PostWallDrawingAction.Invoke(handle);
// Beacons
if (_beacons.Pressed)
{
var rectBuffer = new Vector2(5f, 3f);
// Calculate font size for current zoom level
var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
foreach (var beacon in _navMap.Beacons.Values)
{
var position = beacon.Position - offset;
position = ScalePosition(position with { Y = -position.Y });
var textDimensions = handle.GetDimensions(font, beacon.Text, 1f);
handle.DrawRect(new UIBox2(position - textDimensions / 2 - rectBuffer, position + textDimensions / 2 + rectBuffer), BackgroundColor);
handle.DrawString(font, position - textDimensions / 2, beacon.Text, beacon.Color);
}
}
var curTime = Timing.RealTime;
var blinkFrequency = 1f / 1f;
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
@@ -423,31 +443,11 @@ public partial class NavMapControl : MapGridControl
position = ScalePosition(new Vector2(position.X, -position.Y));
var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale);
var positionOffset = new Vector2(scalingCoefficient * blip.Scale * blip.Texture.Width, scalingCoefficient * blip.Scale * blip.Texture.Height);
var positionOffset = new Vector2(scalingCoefficient * blip.Texture.Width, scalingCoefficient * blip.Texture.Height);
handle.DrawTextureRect(blip.Texture, new UIBox2(position - positionOffset, position + positionOffset), blip.Color);
}
}
// Beacons
if (_beacons.Pressed)
{
var rectBuffer = new Vector2(5f, 3f);
// Calculate font size for current zoom level
var fontSize = (int)Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
foreach (var beacon in _navMap.Beacons.Values)
{
var position = beacon.Position - offset;
position = ScalePosition(position with { Y = -position.Y });
var textDimensions = handle.GetDimensions(font, beacon.Text, 1f);
handle.DrawRect(new UIBox2(position - textDimensions / 2 - rectBuffer, position + textDimensions / 2 + rectBuffer), BackgroundColor);
handle.DrawString(font, position - textDimensions / 2, beacon.Text, beacon.Color);
}
}
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -689,9 +689,6 @@ public partial class NavMapControl : MapGridControl
Vector2i foundTermius;
Vector2i foundOrigin;
if (origin == terminus)
return;
// Does our new line end at the beginning of an existing line?
if (lookup.Remove(terminus, out foundTermius))
{
@@ -742,15 +739,13 @@ public struct NavMapBlip
public Color Color;
public bool Blinks;
public bool Selectable;
public float Scale;
public NavMapBlip(EntityCoordinates coordinates, Texture texture, Color color, bool blinks, bool selectable = true, float scale = 1f)
public NavMapBlip(EntityCoordinates coordinates, Texture texture, Color color, bool blinks, bool selectable = true)
{
Coordinates = coordinates;
Texture = texture;
Color = color;
Blinks = blinks;
Selectable = selectable;
Scale = scale;
}
}

View File

@@ -6,7 +6,6 @@ using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Content.Client.Power;
@@ -105,26 +104,6 @@ public sealed partial class PowerMonitoringWindow
// Update power value
// Don't use SI prefixes, just give the number in W, so that it is readily apparent which consumer is using a lot of power.
button.PowerValue.Text = Loc.GetString("power-monitoring-window-button-value", ("value", Math.Round(entry.PowerValue).ToString("N0")));
// Update battery level if applicable
if (entry.BatteryLevel != null)
{
button.BatteryLevel.Value = entry.BatteryLevel.Value;
button.BatteryLevel.Visible = true;
button.BatteryPercentage.Text = entry.BatteryLevel.Value.ToString("P0");
button.BatteryPercentage.Visible = true;
// Set progress bar color based on percentage
var color = Color.FromHsv(new Vector4(entry.BatteryLevel.Value * 0.33f, 1, 1, 1));
button.BatteryLevel.ForegroundStyleBoxOverride = new StyleBoxFlat { BackgroundColor = color };
}
else
{
button.BatteryLevel.Visible = false;
button.BatteryPercentage.Visible = false;
}
}
private void UpdateEntrySourcesOrLoads(BoxContainer masterContainer, BoxContainer currentContainer, PowerMonitoringConsoleEntry[]? entries, SpriteSpecifier.Texture icon)
@@ -464,11 +443,6 @@ public sealed class PowerMonitoringButton : Button
public BoxContainer MainContainer;
public TextureRect TextureRect;
public Label NameLocalized;
public ProgressBar BatteryLevel;
public PanelContainer BackgroundPanel;
public Label BatteryPercentage;
public Label PowerValue;
public PowerMonitoringButton()
@@ -504,49 +478,6 @@ public sealed class PowerMonitoringButton : Button
MainContainer.AddChild(NameLocalized);
BatteryLevel = new ProgressBar()
{
SetWidth = 47f,
SetHeight = 20f,
Margin = new Thickness(15, 0, 0, 0),
MaxValue = 1,
Visible = false,
BackgroundStyleBoxOverride = new StyleBoxFlat { BackgroundColor = Color.Black },
};
MainContainer.AddChild(BatteryLevel);
BackgroundPanel = new PanelContainer
{
// Draw a half-transparent box over the battery level to make the text more readable.
PanelOverride = new StyleBoxFlat
{
BackgroundColor = new Color(0, 0, 0, 0.9f)
},
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
VerticalExpand = true,
// Box is undersized perfectly compared to the progress bar, so a little bit of the unaffected progress bar is visible.
SetSize = new Vector2(43f, 16f)
};
BatteryLevel.AddChild(BackgroundPanel);
BatteryPercentage = new Label()
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Align = Label.AlignMode.Center,
SetWidth = 45f,
MinWidth = 20f,
Margin = new Thickness(10, -4, 10, 0),
ClipText = true,
Visible = false,
};
BackgroundPanel.AddChild(BatteryPercentage);
PowerValue = new Label()
{
HorizontalAlignment = HAlignment.Right,

View File

@@ -110,7 +110,7 @@ namespace Content.Client.Sandbox
}
// Try copy tile.
if (!_map.TryFindGridAt(_transform.ToMapCoordinates(coords), out var gridUid, out var grid) || !_mapSystem.TryGetTileRef(gridUid, grid, coords, out var tileRef))
return false;
@@ -157,5 +157,10 @@ namespace Content.Client.Sandbox
{
_consoleHost.ExecuteCommand("physics shapes");
}
public void MachineLinking()
{
_consoleHost.ExecuteCommand("signallink");
}
}
}

View File

@@ -28,7 +28,6 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo
public SensorMonitoringWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void UpdateState(ConsoleUIState state)

View File

@@ -54,7 +54,7 @@ public sealed partial class StoreMenu : DefaultWindow
foreach (var ((_, amount), proto) in currency)
{
balanceStr += Loc.GetString("store-ui-balance-display", ("amount", amount),
("currency", Loc.GetString(proto.DisplayName, ("amount", 1)))) + "\n";
("currency", Loc.GetString(proto.DisplayName, ("amount", 1))));
}
BalanceInfo.SetMarkup(balanceStr.TrimEnd());
@@ -63,10 +63,7 @@ public sealed partial class StoreMenu : DefaultWindow
foreach (var type in currency)
{
if (type.Value.CanWithdraw && type.Value.Cash != null && type.Key.Item2 > 0)
{
disabled = false;
break;
}
}
WithdrawButton.Disabled = disabled;

View File

@@ -18,7 +18,7 @@ public sealed partial class StoreWithdrawWindow : DefaultWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private Dictionary<CurrencyPrototype, FixedPoint2> _validCurrencies = new();
private Dictionary<FixedPoint2, CurrencyPrototype> _validCurrencies = new();
private HashSet<CurrencyWithdrawButton> _buttons = new();
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
@@ -36,7 +36,7 @@ public sealed partial class StoreWithdrawWindow : DefaultWindow
if (!_prototypeManager.TryIndex(currency.Key, out var proto))
continue;
_validCurrencies.Add(proto, currency.Value);
_validCurrencies.Add(currency.Value, proto);
}
//this shouldn't ever happen but w/e
@@ -47,17 +47,14 @@ public sealed partial class StoreWithdrawWindow : DefaultWindow
_buttons.Clear();
foreach (var currency in _validCurrencies)
{
if (!currency.Key.CanWithdraw)
continue;
var button = new CurrencyWithdrawButton()
{
Id = currency.Key.ID,
Amount = currency.Value,
Id = currency.Value.ID,
Amount = currency.Key,
MinHeight = 20,
Text = Loc.GetString("store-withdraw-button-ui", ("currency",Loc.GetString(currency.Key.DisplayName, ("amount", currency.Value)))),
Disabled = false,
Text = Loc.GetString("store-withdraw-button-ui", ("currency",Loc.GetString(currency.Value.DisplayName, ("amount", currency.Key)))),
};
button.Disabled = false;
button.OnPressed += args =>
{
OnWithdrawAttempt?.Invoke(args, button.Id, WithdrawSlider.Value);
@@ -68,7 +65,7 @@ public sealed partial class StoreWithdrawWindow : DefaultWindow
ButtonContainer.AddChild(button);
}
var maxWithdrawAmount = _validCurrencies.Values.Max().Int();
var maxWithdrawAmount = _validCurrencies.Keys.Max().Int();
// setup withdraw slider
WithdrawSlider.MinValue = 1;

View File

@@ -110,7 +110,6 @@ namespace Content.Client.Stylesheets
public static readonly Color ButtonColorGoodDefault = Color.FromHex("#3E6C45");
public static readonly Color ButtonColorGoodHovered = Color.FromHex("#31843E");
public static readonly Color ButtonColorGoodDisabled = Color.FromHex("#164420");
//NavMap
public static readonly Color PointRed = Color.FromHex("#B02E26");
@@ -1500,20 +1499,6 @@ namespace Content.Client.Stylesheets
Element<Button>().Class("ButtonColorGreen").Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, ButtonColorGoodHovered),
// Accept button (merge with green button?) ---
Element<Button>().Class("ButtonAccept")
.Prop(Control.StylePropertyModulateSelf, ButtonColorGoodDefault),
Element<Button>().Class("ButtonAccept").Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, ButtonColorGoodDefault),
Element<Button>().Class("ButtonAccept").Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, ButtonColorGoodHovered),
Element<Button>().Class("ButtonAccept").Pseudo(ContainerButton.StylePseudoClassDisabled)
.Prop(Control.StylePropertyModulateSelf, ButtonColorGoodDisabled),
// ---
// Small Button ---

View File

@@ -1,8 +0,0 @@
using Content.Shared.Telephone;
namespace Content.Client.Telephone;
public sealed class TelephoneSystem : SharedTelephoneSystem
{
}

View File

@@ -104,7 +104,7 @@ public sealed class TippyUIController : UIController
? -WaddleRotation
: WaddleRotation;
if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step) && step.FootstepSoundCollection != null)
if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step))
{
var audioParams = step.FootstepSoundCollection.Params
.AddVolume(-7f)

View File

@@ -1,34 +0,0 @@
using System.Linq;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.RichText;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.RichText;
/// <summary>
/// Sets the font to a monospaced variant
/// </summary>
public sealed class MonoTag : IMarkupTag
{
[ValidatePrototypeId<FontPrototype>] public const string MonoFont = "Monospace";
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public string Name => "mono";
/// <inheritdoc/>
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
{
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, MonoFont);
context.Font.Push(font);
}
/// <inheritdoc/>
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
{
context.Font.Pop();
}
}

View File

@@ -572,10 +572,6 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
_window.OnClose += () => { OnClose?.Invoke(); };
_window.OnOpen += () => { OnOpen?.Invoke(); };
_window.Contents.AddChild(_chatPanel);
var introText = Loc.GetString("bwoink-system-introductory-message");
var introMessage = new SharedBwoinkSystem.BwoinkTextMessage( _ownerId, SharedBwoinkSystem.SystemUserId, introText);
Receive(introMessage);
}
public void Dispose()

View File

@@ -1,5 +1,4 @@
using System.Numerics;
using Content.Client.Administration.Managers;
using Content.Client.Administration.Managers;
using Content.Client.Gameplay;
using Content.Client.Markers;
using Content.Client.Sandbox;
@@ -8,7 +7,9 @@ using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.DecalPlacer;
using Content.Client.UserInterface.Systems.Sandbox.Windows;
using Content.Shared.Input;
using Content.Shared.Silicons.StationAi;
using JetBrains.Annotations;
using Robust.Client.Console;
using Robust.Client.Debugging;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -108,13 +109,9 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
private void EnsureWindow()
{
if (_window is { Disposed: false })
if(_window is { Disposed: false })
return;
_window = UIManager.CreateWindow<SandboxWindow>();
// Pre-center the window without forcing it to the center every time.
_window.OpenCentered();
_window.Close();
_window.OnOpen += () => { SandboxButton!.Pressed = true; };
_window.OnClose += () => { SandboxButton!.Pressed = false; };
_window.ToggleLightButton.Pressed = !_light.Enabled;
@@ -152,6 +149,7 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
_window.ToggleSubfloorButton.OnPressed += _ => _sandbox.ToggleSubFloor();
_window.ShowMarkersButton.OnPressed += _ => _sandbox.ShowMarkers();
_window.ShowBbButton.OnPressed += _ => _sandbox.ShowBb();
_window.MachineLinkingButton.OnPressed += _ => _sandbox.MachineLinking();
}
private void CheckSandboxVisibility()
@@ -166,7 +164,7 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
{
if (_window != null)
{
_window.Close();
_window.Dispose();
_window = null;
}
@@ -211,7 +209,7 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
if (_sandbox.SandboxAllowed && _window.IsOpen != true)
{
UIManager.ClickSound();
_window.Open();
_window.OpenCentered();
}
else
{

View File

@@ -4,24 +4,20 @@
Title="{Loc sandbox-window-title}"
Resizable="False">
<BoxContainer Orientation="Vertical" SeparationOverride="4">
<Label Text="{Loc sandbox-window-map-editing-label}"/>
<Button Name="SpawnTilesButton" Access="Public" Text="{Loc sandbox-window-spawn-tiles-button}"/>
<Button Name="AiOverlayButton" Access="Public" Text="{Loc sandbox-window-ai-overlay-button}" ToggleMode="True"/>
<Button Name="RespawnButton" Access="Public" Text="{Loc sandbox-window-respawn-button}"/>
<Button Name="SpawnEntitiesButton" Access="Public" Text="{Loc sandbox-window-spawn-entities-button}"/>
<Button Name="SpawnTilesButton" Access="Public" Text="{Loc sandbox-window-spawn-tiles-button}"/>
<Button Name="SpawnDecalsButton" Access="Public" Text="{Loc sandbox-window-spawn-decals-button}"/>
<Label Text="{Loc sandbox-window-visibility-label}"/>
<Button Name="GiveFullAccessButton" Access="Public" Text="{Loc sandbox-window-grant-full-access-button}"/>
<Button Name="GiveAghostButton" Access="Public" Text="{Loc sandbox-window-ghost-button}"/>
<Button Name="ToggleLightButton" Access="Public" Text="{Loc sandbox-window-toggle-lights-button}" ToggleMode="True"/>
<Button Name="ToggleFovButton" Access="Public" Text="{Loc sandbox-window-toggle-fov-button}" ToggleMode="True"/>
<Button Name="ToggleShadowsButton" Access="Public" Text="{Loc sandbox-window-toggle-shadows-button}" ToggleMode="True"/>
<Button Name="ToggleSubfloorButton" Access="Public" Text="{Loc sandbox-window-toggle-subfloor-button}" ToggleMode="True"/>
<Button Name="AiOverlayButton" Access="Public" Text="{Loc sandbox-window-ai-overlay-button}" ToggleMode="True"/>
<Button Name="SuicideButton" Access="Public" Text="{Loc sandbox-window-toggle-suicide-button}" ToggleMode="True"/>
<Button Name="ShowMarkersButton" Access="Public" Text="{Loc sandbox-window-show-spawns-button}" ToggleMode="True"/>
<Button Name="ShowBbButton" Access="Public" Text="{Loc sandbox-window-show-bb-button}" ToggleMode="True"/>
<Label Text="{Loc sandbox-window-your-character-label}"/>
<Button Name="GiveAghostButton" Access="Public" Text="{Loc sandbox-window-ghost-button}"/>
<Button Name="GiveFullAccessButton" Access="Public" Text="{Loc sandbox-window-grant-full-access-button}"/>
<Button Name="SuicideButton" Access="Public" Text="{Loc sandbox-window-toggle-suicide-button}"/>
<Button Name="RespawnButton" Access="Public" Text="{Loc sandbox-window-respawn-button}"/>
<Button Name="MachineLinkingButton" Access="Public" Text="{Loc sandbox-window-link-machines-button}" ToggleMode="True"/>
</BoxContainer>
</windows:SandboxWindow>

View File

@@ -77,7 +77,6 @@ public sealed partial class GunSystem : SharedGunSystem
base.Initialize();
UpdatesOutsidePrediction = true;
SubscribeLocalEvent<AmmoCounterComponent, ItemStatusCollectMessage>(OnAmmoCounterCollect);
SubscribeLocalEvent<AmmoCounterComponent, UpdateClientAmmoEvent>(OnUpdateClientAmmo);
SubscribeAllEvent<MuzzleFlashEvent>(OnMuzzleFlash);
// Plays animated effects on the client.
@@ -87,11 +86,6 @@ public sealed partial class GunSystem : SharedGunSystem
InitializeSpentAmmo();
}
private void OnUpdateClientAmmo(EntityUid uid, AmmoCounterComponent ammoComp, ref UpdateClientAmmoEvent args)
{
UpdateAmmoCount(uid, ammoComp);
}
private void OnMuzzleFlash(MuzzleFlashEvent args)
{
var gunUid = GetEntity(args.Uid);

View File

@@ -1,7 +0,0 @@
using Content.Shared._CP14.Knowledge;
namespace Content.Client._CP14.Knowledge;
public sealed partial class ClientCP14KnowledgeSystem : SharedCP14KnowledgeSystem
{
}

View File

@@ -80,9 +80,6 @@ public sealed class CP14ClientModularCraftSystem : CP14SharedModularCraftSystem
start.Comp.RevealedLayers.Add(keyCode);
var index = sprite.AddLayer(defaultLayer);
sprite.LayerMapSet(keyCode, index);
if (indexedPart.Color is not null)
sprite.LayerSetColor(keyCode, indexedPart.Color.Value);
}
else
{
@@ -151,7 +148,6 @@ public sealed class CP14ClientModularCraftSystem : CP14SharedModularCraftSystem
{
RsiPath = indexedPart.RsiPath,
State = state,
Color = indexedPart.Color,
};
var key = $"{defaultKey}-{counterPart}-default";
@@ -207,7 +203,6 @@ public sealed class CP14ClientModularCraftSystem : CP14SharedModularCraftSystem
{
RsiPath = indexedPart.RsiPath,
State = state,
Color = indexedPart.Color,
};
var key = $"{defaultKey}-{counterPart}-default";

View File

@@ -16,12 +16,11 @@
<Label Text="{Loc 'cp14-ui-options-main-graphics-label'}"
StyleClasses="LabelKeyText"/>
<CheckBox Name="WaveShaderEnabled"
Text="{Loc 'cp14-ui-options-main-graphics-wave-shader'}"/>
<!-- TODO: Wave shader tooltip -->
<CheckBox Name="PostProcessCheckBox"
Text="{Loc 'cp14-ui-options-postprocess'}"
ToolTip="{Loc 'cp14-ui-options-postprocess-tooltip'}"/>
<!-- CP14 Wave shader settings -->
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'cp14-ui-options-main-graphics-wave-shader'}" Margin="0 0 4 0"/>
<CheckBox Name="WaveShaderEnabled"/>
</BoxContainer>
</BoxContainer>
</ScrollContainer>

View File

@@ -13,7 +13,6 @@ public sealed partial class CP14OptionsMenuMainTab : Control
RobustXamlLoader.Load(this);
Control.AddOptionCheckBox(CP14ConfigVars.WaveShaderEnabled, WaveShaderEnabled);
Control.AddOptionCheckBox(CP14ConfigVars.PostProcess, PostProcessCheckBox);
Control.Initialize();
}

View File

@@ -1,77 +0,0 @@
using Content.Shared._CP14.Configuration;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
// This overlay serves as the foundational post processing overlay.
// Ideally, for performance reasons, post processing designed to be present at all times, such as additive light blending or tonemapping, should be done as part of a single shader pass.
public sealed class CP14BasePostProcessOverlay : Overlay
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _basePostProcessShader;
public CP14BasePostProcessOverlay()
{
IoCManager.InjectDependencies(this);
_basePostProcessShader = _prototypeManager.Index<ShaderPrototype>("BasePostProcess").InstanceUnique();
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_configManager.GetCVar(CP14ConfigVars.PostProcess))
return false;
if (!_entityManager.TryGetComponent(_playerManager.LocalSession?.AttachedEntity, out EyeComponent? eyeComp))
return false;
if (args.Viewport.Eye != eyeComp.Eye)
return false;
if (!_lightManager.Enabled || !eyeComp.Eye.DrawLight || !eyeComp.Eye.DrawFov)
return false;
var playerEntity = _playerManager.LocalSession?.AttachedEntity;
if (playerEntity == null)
return false;
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null)
return;
if (args.Viewport.Eye == null)
return;
var playerEntity = _playerManager.LocalSession?.AttachedEntity;
var worldHandle = args.WorldHandle;
var viewport = args.WorldBounds;
_basePostProcessShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
_basePostProcessShader.SetParameter("LIGHT_TEXTURE", args.Viewport.LightRenderTarget.Texture);
_basePostProcessShader.SetParameter("Zoom", args.Viewport.Eye.Zoom.X);
worldHandle.UseShader(_basePostProcessShader);
worldHandle.DrawRect(viewport, Color.White);
worldHandle.UseShader(null);
}
}

View File

@@ -12,9 +12,6 @@ public sealed class CP14WorkbenchBoundUserInterface : BoundUserInterface
{
private CP14WorkbenchWindow? _window;
[ViewVariables]
private string _search = string.Empty;
public CP14WorkbenchBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
@@ -26,12 +23,6 @@ public sealed class CP14WorkbenchBoundUserInterface : BoundUserInterface
_window = this.CreateWindow<CP14WorkbenchWindow>();
_window.OnCraft += entry => SendMessage(new CP14WorkbenchUiCraftMessage(entry.ProtoId));
_window.OnTextUpdated += search =>
{
_search = search.Trim().ToLowerInvariant();
_window.UpdateFilter(_search);
};
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -41,7 +32,7 @@ public sealed class CP14WorkbenchBoundUserInterface : BoundUserInterface
switch (state)
{
case CP14WorkbenchUiRecipesState recipesState:
_window?.UpdateRecipes(recipesState, _search);
_window?.UpdateRecipes(recipesState);
break;
}
}

View File

@@ -1,20 +1,10 @@
<Control xmlns="https://spacestation14.io">
<GridContainer Columns="2">
<EntityPrototypeView
Name="EntityView"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
Scale="2,2"
HorizontalAlignment="Left"
VerticalExpand="True" />
<TextureRect
Name="View"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
HorizontalAlignment="Left"
Stretch="KeepAspectCentered" />
<Label Name="Name" />
<TextureRect Name="View"
Margin="0,0,4,0"
MinSize="48 48"
HorizontalAlignment="Left"
Stretch="KeepAspectCentered"/>
<Label Name="Name"/>
</GridContainer>
</Control>

View File

@@ -31,9 +31,7 @@ public sealed partial class CP14WorkbenchRecipeControl : Control
{
var entityName = prototype.Name;
Name.Text = count <= 1 ? entityName : $"{entityName} x{count}";
View.Visible = false;
EntityView.SetPrototype(prototype);
View.Texture = _sprite.GetPrototypeIcon(prototype).Default;
}
public CP14WorkbenchRecipeControl(StackPrototype prototype, int count) : this()
@@ -45,7 +43,6 @@ public sealed partial class CP14WorkbenchRecipeControl : Control
if (icon is null)
return;
EntityView.Visible = false;
View.Texture = _sprite.Frame0(icon);
}
}

View File

@@ -1,13 +1,11 @@
<Control xmlns="https://spacestation14.io">
<Button Name="Button">
<GridContainer Columns="2">
<EntityPrototypeView Name="View"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
Scale="2,2"
HorizontalAlignment="Center"
VerticalExpand="True"/>
<TextureRect Name="View"
Margin="0,0,4,0"
MinSize="48 48"
HorizontalAlignment="Left"
Stretch="KeepAspectCentered"/>
<Label Name="Name"/>
</GridContainer>
</Button>

View File

@@ -59,6 +59,6 @@ public sealed partial class CP14WorkbenchRequirementControl : Control
private void UpdateView()
{
View.SetPrototype(_recipePrototype.Result);
View.Texture = _sprite.GetPrototypeIcon(_recipePrototype.Result).Default;
}
}

View File

@@ -2,68 +2,59 @@
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'cp14-workbench-ui-title'}"
SetSize="700 500"
MinSize="700 300">
<BoxContainer Orientation="Horizontal">
MinSize="700 600">
<BoxContainer Orientation="Vertical">
<!-- Main -->
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal">
<!-- Product list (left side UI) -->
<BoxContainer SizeFlagsStretchRatio="0.5" HorizontalExpand="True" Orientation="Vertical" Margin="0 0 10 0">
<!-- Search Bar -->
<LineEdit Name="SearchBar" Margin="4" PlaceHolder="Search" HorizontalExpand="True" />
<GridContainer HorizontalExpand="True" VerticalExpand="True" Columns="2">
<!-- Crafts container -->
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
<BoxContainer Name="CraftsContainer" Orientation="Vertical" HorizontalExpand="True" />
<BoxContainer Name="CraftsContainer" Orientation="Vertical" HorizontalExpand="True"/>
</ScrollContainer>
</BoxContainer>
<!-- Craft view (right side UI) -->
<BoxContainer SizeFlagsStretchRatio="0.5" Orientation="Vertical" HorizontalExpand="True"
VerticalExpand="True">
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
<!-- Background -->
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#41332f" />
</PanelContainer.PanelOverride>
<!-- Craft view -->
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
<!-- Background -->
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#41332f"/>
</PanelContainer.PanelOverride>
<!-- Content -->
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<!-- Item info -->
<!-- icon -->
<EntityPrototypeView Name="ItemView"
Scale="2,2"
<!-- Content -->
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<!-- Item info -->
<GridContainer HorizontalExpand="True" Columns="2">
<!-- Left panel - icon -->
<TextureRect Name="ItemView"
Margin="0,0,4,0"
MinSize="64 64"
MaxSize="64 64"
HorizontalAlignment="Left" />
HorizontalAlignment="Left"
Stretch="KeepAspectCentered"/>
<!-- name & description -->
<BoxContainer Margin="5 0 0 0" HorizontalExpand="True" VerticalExpand="True"
Orientation="Vertical">
<RichTextLabel Name="ItemName" Margin="5" />
<RichTextLabel Name="ItemDescription" Margin="5" />
<!-- Right panel - name & description -->
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<Label Name="ItemName" Text="Name"/>
<Label Name="ItemDescription" Text="Description" ClipText="True"/>
</BoxContainer>
</GridContainer>
<controls:HLine Color="#404040" Thickness="2" Margin="0 5"/>
<!-- Required title -->
<Label Text="{Loc 'cp14-workbench-recipe-list'}"/>
<!-- Craft requirements content -->
<!-- Added by code -->
<BoxContainer Name="ItemRequirements" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"/>
<controls:HLine Color="#404040" Thickness="2" Margin="0 5"/>
<!-- Craft button -->
<Button Name="CraftButton" Text="{Loc 'cp14-workbench-craft'}"/>
</BoxContainer>
<controls:HLine Color="#404040" Thickness="2" Margin="0 5" />
<!-- Required title -->
<Label Margin="5 0 0 0" Text="{Loc 'cp14-workbench-recipe-list'}" />
<!-- Craft requirements content -->
<!-- Added by code -->
<BoxContainer
Margin="5 0 0 0"
Name="ItemRequirements"
Orientation="Vertical" VerticalExpand="True"
HorizontalExpand="True" />
<controls:HLine Color="#404040" Thickness="5" Margin="0 5" />
<!-- Craft button -->
<Button Name="CraftButton" Text="{Loc 'cp14-workbench-craft'}" />
</BoxContainer>
</PanelContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</GridContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -19,22 +19,19 @@ public sealed partial class CP14WorkbenchWindow : DefaultWindow
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private CP14WorkbenchUiRecipesState? _cachedState;
public event Action<CP14WorkbenchUiRecipesEntry>? OnCraft;
public event Action<string>? OnTextUpdated;
private CP14WorkbenchUiRecipesEntry? _selectedEntry;
private readonly SpriteSystem _sprite;
private CP14WorkbenchUiRecipesEntry? _selectedEntry ;
public CP14WorkbenchWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
SearchBar.OnTextChanged += _ =>
{
OnTextUpdated?.Invoke(SearchBar.Text);
};
_sprite = _entity.System<SpriteSystem>();
CraftButton.OnPressed += _ =>
{
if (_selectedEntry is null)
@@ -44,34 +41,13 @@ public sealed partial class CP14WorkbenchWindow : DefaultWindow
};
}
public void UpdateFilter(string? search)
public void UpdateRecipes(CP14WorkbenchUiRecipesState recipesState)
{
if (_cachedState is null)
return;
UpdateRecipes(_cachedState, search);
}
public void UpdateRecipes(CP14WorkbenchUiRecipesState recipesState, string? search = null)
{
_cachedState = recipesState;
CraftsContainer.RemoveAllChildren();
List<CP14WorkbenchUiRecipesEntry> uncraftableList = new();
foreach (var entry in recipesState.Recipes)
{
if (search is not null && search != "")
{
if (!_prototype.TryIndex(entry.ProtoId, out var indexedEntry))
continue;
if (!_prototype.TryIndex(indexedEntry.Result, out var indexedResult))
continue;
if (!indexedResult.Name.Contains(search))
continue;
}
if (entry.Craftable)
{
var control = new CP14WorkbenchRequirementControl(entry);
@@ -113,7 +89,7 @@ public sealed partial class CP14WorkbenchWindow : DefaultWindow
var result = _prototype.Index(recipe.Result);
ItemView.SetPrototype(recipe.Result);
ItemView.Texture = _sprite.GetPrototypeIcon(recipe.Result).Default;
ItemName.Text = result.Name;
ItemDescription.Text = result.Description;

View File

@@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared;
using Robust.Shared.Audio.Components;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
@@ -217,17 +216,14 @@ namespace Content.IntegrationTests.Tests
/// generally not spawn unrelated / detached entities. Any entities that do get spawned should be parented to
/// the spawned entity (e.g., in a container). If an entity needs to spawn an entity somewhere in null-space,
/// it should delete that entity when it is no longer required. This test mainly exists to prevent "entity leak"
/// bugs, where spawning some entity starts spawning unrelated entities in null space that stick around after
/// the original entity is gone.
///
/// Note that this isn't really a strict requirement, and there are probably quite a few edge cases. Its a pretty
/// crude test to try catch issues like this, and possibly should just be disabled.
/// bugs, where spawning some entity starts spawning unrelated entities in null space.
/// </remarks>
[Test]
public async Task SpawnAndDeleteEntityCountTest()
{
var settings = new PoolSettings { Connected = true, Dirty = true };
await using var pair = await PoolManager.GetServerClient(settings);
var mapManager = pair.Server.ResolveDependency<IMapManager>();
var mapSys = pair.Server.System<SharedMapSystem>();
var server = pair.Server;
var client = pair.Client;
@@ -265,9 +261,6 @@ namespace Content.IntegrationTests.Tests
await pair.RunTicksSync(3);
// We consider only non-audio entities, as some entities will just play sounds when they spawn.
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
foreach (var protoId in protoIds)
{
// TODO fix ninja
@@ -275,8 +268,8 @@ namespace Content.IntegrationTests.Tests
if (protoId == "MobHumanSpaceNinja")
continue;
var count = Count(server.EntMan);
var clientCount = Count(client.EntMan);
var count = server.EntMan.EntityCount;
var clientCount = client.EntMan.EntityCount;
EntityUid uid = default;
await server.WaitPost(() => uid = server.EntMan.SpawnEntity(protoId, coords));
await pair.RunTicksSync(3);
@@ -284,30 +277,30 @@ namespace Content.IntegrationTests.Tests
// If the entity deleted itself, check that it didn't spawn other entities
if (!server.EntMan.EntityExists(uid))
{
if (Count(server.EntMan) != count)
if (server.EntMan.EntityCount != count)
{
Assert.Fail($"Server prototype {protoId} failed on deleting itself");
}
if (Count(client.EntMan) != clientCount)
if (client.EntMan.EntityCount != clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" +
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
$"Server was {count}.");
}
continue;
}
// Check that the number of entities has increased.
if (Count(server.EntMan) <= count)
if (server.EntMan.EntityCount <= count)
{
Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase");
}
if (Count(client.EntMan) <= clientCount)
if (client.EntMan.EntityCount <= clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" +
$"Expected at least {clientCount} and found {Count(client.EntMan)}. " +
$"Expected at least {clientCount} and found {client.EntMan.EntityCount}. " +
$"Server was {count}");
}
@@ -315,15 +308,15 @@ namespace Content.IntegrationTests.Tests
await pair.RunTicksSync(3);
// Check that the number of entities has gone back to the original value.
if (Count(server.EntMan) != count)
if (server.EntMan.EntityCount != count)
{
Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly");
}
if (Count(client.EntMan) != clientCount)
if (client.EntMan.EntityCount != clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" +
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
$"Server was {count}.");
}
}

View File

@@ -1,132 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Lathe;
using Content.Shared.Materials;
using Content.Shared.Prototypes;
using Content.Shared.Research.Prototypes;
using Content.Shared.Whitelist;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Lathe;
[TestFixture]
public sealed class LatheTest
{
[Test]
public async Task TestLatheRecipeIngredientsFitLathe()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var mapData = await pair.CreateTestMap();
var entMan = server.EntMan;
var protoMan = server.ProtoMan;
var compFactory = server.ResolveDependency<IComponentFactory>();
var materialStorageSystem = server.System<SharedMaterialStorageSystem>();
var whitelistSystem = server.System<EntityWhitelistSystem>();
await server.WaitAssertion(() =>
{
// Find all the lathes
var latheProtos = protoMan.EnumeratePrototypes<EntityPrototype>()
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => p.HasComponent<LatheComponent>());
// Find every EntityPrototype that can be inserted into a MaterialStorage
var materialEntityProtos = protoMan.EnumeratePrototypes<EntityPrototype>()
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => p.HasComponent<PhysicalCompositionComponent>());
// Spawn all of the above material EntityPrototypes - we need actual entities to do whitelist checks
var materialEntities = new List<EntityUid>(materialEntityProtos.Count());
foreach (var materialEntityProto in materialEntityProtos)
{
materialEntities.Add(entMan.SpawnEntity(materialEntityProto.ID, mapData.GridCoords));
}
Assert.Multiple(() =>
{
// Check each lathe individually
foreach (var latheProto in latheProtos)
{
if (!latheProto.TryGetComponent<LatheComponent>(out var latheComp, compFactory))
continue;
if (!latheProto.TryGetComponent<MaterialStorageComponent>(out var storageComp, compFactory))
continue;
// Test which material-containing entities are accepted by this lathe
var acceptedMaterials = new HashSet<ProtoId<MaterialPrototype>>();
foreach (var materialEntity in materialEntities)
{
Assert.That(entMan.TryGetComponent<PhysicalCompositionComponent>(materialEntity, out var compositionComponent));
if (whitelistSystem.IsWhitelistFail(storageComp.Whitelist, materialEntity))
continue;
// Mark the lathe as accepting each material in the entity
foreach (var (material, _) in compositionComponent.MaterialComposition)
{
acceptedMaterials.Add(material);
}
}
// Collect all the recipes assigned to this lathe
var recipes = new List<ProtoId<LatheRecipePrototype>>();
recipes.AddRange(latheComp.StaticRecipes);
recipes.AddRange(latheComp.DynamicRecipes);
if (latheProto.TryGetComponent<EmagLatheRecipesComponent>(out var emagRecipesComp, compFactory))
{
recipes.AddRange(emagRecipesComp.EmagStaticRecipes);
recipes.AddRange(emagRecipesComp.EmagDynamicRecipes);
}
// Check each recipe assigned to this lathe
foreach (var recipeId in recipes)
{
Assert.That(protoMan.TryIndex(recipeId, out var recipeProto));
// Track the total material volume of the recipe
var totalQuantity = 0;
// Check each material called for by the recipe
foreach (var (materialId, quantity) in recipeProto.Materials)
{
Assert.That(protoMan.TryIndex(materialId, out var materialProto));
// Make sure the material is accepted by the lathe
Assert.That(acceptedMaterials, Does.Contain(materialId), $"Lathe {latheProto.ID} has recipe {recipeId} but does not accept any materials containing {materialId}");
totalQuantity += quantity;
}
// Make sure the recipe doesn't call for more material than the lathe can hold
if (storageComp.StorageLimit != null)
Assert.That(totalQuantity, Is.LessThanOrEqualTo(storageComp.StorageLimit), $"Lathe {latheProto.ID} has recipe {recipeId} which calls for {totalQuantity} units of materials but can only hold {storageComp.StorageLimit}");
}
}
});
});
await pair.CleanReturnAsync();
}
[Test]
public async Task AllLatheRecipesValidTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var proto = server.ProtoMan;
Assert.Multiple(() =>
{
foreach (var recipe in proto.EnumeratePrototypes<LatheRecipePrototype>())
{
if (recipe.Result == null)
Assert.That(recipe.ResultReagents, Is.Not.Null, $"Recipe '{recipe.ID}' has no result or result reagents.");
}
});
await pair.CleanReturnAsync();
}
}

View File

@@ -50,6 +50,7 @@ namespace Content.IntegrationTests.Tests
"MeteorArena",
//CrystallEdge maps
"Village",
"Comoss",
//CrystallEdge Map replacement end
};

View File

@@ -96,6 +96,26 @@ public sealed class ResearchTest
});
});
await pair.CleanReturnAsync();
}
[Test]
public async Task AllLatheRecipesValidTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var proto = server.ResolveDependency<IPrototypeManager>();
Assert.Multiple(() =>
{
foreach (var recipe in proto.EnumeratePrototypes<LatheRecipePrototype>())
{
if (recipe.Result == null)
Assert.That(recipe.ResultReagents, Is.Not.Null, $"Recipe '{recipe.ID}' has no result or result reagents.");
}
});
await pair.CleanReturnAsync();
}*/
}

View File

@@ -79,11 +79,11 @@ public sealed class StoreTests
var discountComponent = entManager.GetComponent<StoreDiscountComponent>(pda);
Assert.That(
discountComponent.Discounts,
Has.Exactly(6).Items,
$"After applying discount total discounted items count was expected to be '6' "
Has.Exactly(3).Items,
$"After applying discount total discounted items count was expected to be '3' "
+ $"but was actually {discountComponent.Discounts.Count}- this can be due to discount "
+ $"categories settings (maxItems, weight) not being realistically set, or default "
+ $"discounted count being changed from '6' in StoreDiscountSystem.InitializeDiscounts."
+ $"discounted count being changed from '3' in StoreDiscountSystem.InitializeDiscounts."
);
var discountedListingItems = storeComponent.FullListingsCatalog
.Where(x => x.IsCostModified)

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Reflection;
using Content.Server.Administration.Managers;
using System.Linq;
using Robust.Shared.Toolshed;
namespace Content.IntegrationTests.Tests.Toolshed;
@@ -11,23 +10,10 @@ public sealed class AdminTest : ToolshedTest
[Test]
public async Task AllCommandsHavePermissions()
{
var toolMan = Server.ResolveDependency<ToolshedManager>();
var admin = Server.ResolveDependency<IAdminManager>();
var ignored = new HashSet<Assembly>()
{typeof(LocTest).Assembly, typeof(Robust.UnitTesting.Shared.Toolshed.LocTest).Assembly};
await Server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
foreach (var cmd in toolMan.DefaultEnvironment.AllCommands())
{
if (ignored.Contains(cmd.Cmd.GetType().Assembly))
continue;
Assert.That(admin.TryGetCommandFlags(cmd, out _), $"Command does not have admin permissions set up: {cmd.FullName()}");
}
});
Assert.That(InvokeCommand("cmd:list where { acmd:perms isnull }", out var res));
Assert.That((IEnumerable<CommandSpec>) res, Is.Empty, "All commands must have admin permissions set up.");
});
}
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Toolshed;
@@ -14,27 +14,10 @@ public sealed class LocTest : ToolshedTest
[Test]
public async Task AllCommandsHaveDescriptions()
{
var locMan = Server.ResolveDependency<ILocalizationManager>();
var toolMan = Server.ResolveDependency<ToolshedManager>();
var locStrings = new HashSet<string>();
var ignored = new HashSet<Assembly>()
{typeof(LocTest).Assembly, typeof(Robust.UnitTesting.Shared.Toolshed.LocTest).Assembly};
await Server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
foreach (var cmd in toolMan.DefaultEnvironment.AllCommands())
{
if (ignored.Contains(cmd.Cmd.GetType().Assembly))
continue;
var descLoc = cmd.DescLocStr();
Assert.That(locStrings.Add(descLoc), $"Duplicate command description key: {descLoc}");
Assert.That(locMan.TryGetString(descLoc, out _), $"Failed to get command description for command {cmd.FullName()}");
}
});
Assert.That(InvokeCommand("cmd:list where { cmd:descloc loc:tryloc isnull }", out var res));
Assert.That((IEnumerable<CommandSpec>)res!, Is.Empty, "All commands must have localized descriptions.");
});
}
}

View File

@@ -74,15 +74,15 @@ public abstract class ToolshedTest : IInvocationContext
return (T) res!;
}
protected void ParseCommand(string command, Type? inputType = null, Type? expectedType = null)
protected void ParseCommand(string command, Type? inputType = null, Type? expectedType = null, bool once = false)
{
var parser = new ParserContext(command, Toolshed);
var success = CommandRun.TryParse(parser, inputType, expectedType, out _);
var success = CommandRun.TryParse(false, parser, inputType, expectedType, once, out _, out _, out var error);
if (parser.Error is not null)
ReportError(parser.Error);
if (error is not null)
ReportError(error);
if (parser.Error is null)
if (error is null)
Assert.That(success, $"Parse failed despite no error being reported. Parsed {command}");
}
@@ -153,28 +153,11 @@ public abstract class ToolshedTest : IInvocationContext
return _errors;
}
public bool HasErrors => _errors.Count > 0;
public void ClearErrors()
{
_errors.Clear();
}
public object? ReadVar(string name)
{
return Variables.GetValueOrDefault(name);
}
public void WriteVar(string name, object? value)
{
Variables[name] = value;
}
public IEnumerable<string> GetVars()
{
return Variables.Keys;
}
public Dictionary<string, object?> Variables { get; } = new();
protected void ExpectError(Type err)

View File

@@ -10,7 +10,11 @@ namespace Content.Server.Access;
public sealed class AddAccessLogCommand : ToolshedCommand
{
[CommandImplementation]
public void AddAccessLog(IInvocationContext ctx, EntityUid input, float seconds, string accessor)
public void AddAccessLog(
[CommandInvocationContext] IInvocationContext ctx,
[CommandArgument] EntityUid input,
[CommandArgument] float seconds,
[CommandArgument] ValueRef<string> accessor)
{
var accessReader = EnsureComp<AccessReaderComponent>(input);
@@ -19,14 +23,19 @@ public sealed class AddAccessLogCommand : ToolshedCommand
ctx.WriteLine($"WARNING: Surpassing the limit of the log by {accessLogCount - accessReader.AccessLogLimit+1} entries!");
var accessTime = TimeSpan.FromSeconds(seconds);
accessReader.AccessLog.Enqueue(new AccessRecord(accessTime, accessor));
var accessName = accessor.Evaluate(ctx)!;
accessReader.AccessLog.Enqueue(new AccessRecord(accessTime, accessName));
ctx.WriteLine($"Successfully added access log to {input} with this information inside:\n " +
$"Time of access: {accessTime}\n " +
$"Accessed by: {accessor}");
$"Accessed by: {accessName}");
}
[CommandImplementation]
public void AddAccessLogPiped(IInvocationContext ctx, [PipedArgument] EntityUid input, float seconds, string accessor)
public void AddAccessLogPiped(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] EntityUid input,
[CommandArgument] float seconds,
[CommandArgument] ValueRef<string> accessor)
{
AddAccessLog(ctx, input, seconds, accessor);
}

View File

@@ -408,17 +408,6 @@ namespace Content.Server.Administration.Managers
}
private async Task<(AdminData dat, int? rankId, bool specialLogin)?> LoadAdminData(ICommonSession session)
{
var result = await LoadAdminDataCore(session);
// Make sure admin didn't disconnect while data was loading.
if (session.Status != SessionStatus.InGame)
return null;
return result;
}
private async Task<(AdminData dat, int? rankId, bool specialLogin)?> LoadAdminDataCore(ICommonSession session)
{
var promoteHost = IsLocal(session) && _cfg.GetCVar(CCVars.ConsoleLoginLocal)
|| _promotedPlayers.Contains(session.UserId)

View File

@@ -337,15 +337,10 @@ public sealed class AdminSystem : EntitySystem
private void UpdatePanicBunker()
{
var hasAdmins = false;
foreach (var admin in _adminManager.AllAdmins)
{
if (_adminManager.HasAdminFlag(admin, AdminFlags.Admin, includeDeAdmin: PanicBunker.CountDeadminnedAdmins))
{
hasAdmins = true;
break;
}
}
var admins = PanicBunker.CountDeadminnedAdmins
? _adminManager.AllAdmins
: _adminManager.ActiveAdmins;
var hasAdmins = admins.Any();
// TODO Fix order dependent Cvars
// Please for the sake of my sanity don't make cvars & order dependent.

View File

@@ -204,7 +204,6 @@ public sealed partial class AdminVerbSystem
var recharger = EnsureComp<BatterySelfRechargerComponent>(args.Target);
recharger.AutoRecharge = true;
recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
recharger.AutoRechargePause = false; // No delay.
},
Impact = LogImpact.Medium,
Message = Loc.GetString("admin-trick-infinite-battery-object-description"),
@@ -604,7 +603,6 @@ public sealed partial class AdminVerbSystem
recharger.AutoRecharge = true;
recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
recharger.AutoRechargePause = false; // No delay.
}
},
Impact = LogImpact.Extreme,

View File

@@ -7,7 +7,7 @@ namespace Content.Server.Administration.Toolshed;
public sealed class MarkedCommand : ToolshedCommand
{
[CommandImplementation]
public IEnumerable<EntityUid> Marked(IInvocationContext ctx)
public IEnumerable<EntityUid> Marked([CommandInvocationContext] IInvocationContext ctx)
{
var res = (IEnumerable<EntityUid>?)ctx.ReadVar("marked");
res ??= Array.Empty<EntityUid>();

View File

@@ -23,7 +23,7 @@ public sealed class RejuvenateCommand : ToolshedCommand
}
[CommandImplementation]
public void Rejuvenate(IInvocationContext ctx)
public void Rejuvenate([CommandInvocationContext] IInvocationContext ctx)
{
_rejuvenate ??= GetSys<RejuvenateSystem>();
if (ExecutingEntity(ctx) is not { } ent)

View File

@@ -8,7 +8,6 @@ using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Toolshed.TypeParsers;
using System.Linq;
using Robust.Shared.Prototypes;
namespace Content.Server.Administration.Toolshed;
@@ -18,38 +17,48 @@ public sealed class SolutionCommand : ToolshedCommand
private SharedSolutionContainerSystem? _solutionContainer;
[CommandImplementation("get")]
public SolutionRef? Get([PipedArgument] EntityUid input, string name)
public SolutionRef? Get(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] EntityUid input,
[CommandArgument] ValueRef<string> name
)
{
_solutionContainer ??= GetSys<SharedSolutionContainerSystem>();
if (_solutionContainer.TryGetSolution(input, name, out var solution))
if (_solutionContainer.TryGetSolution(input, name.Evaluate(ctx)!, out var solution))
return new SolutionRef(solution.Value);
return null;
}
[CommandImplementation("get")]
public IEnumerable<SolutionRef> Get([PipedArgument] IEnumerable<EntityUid> input, string name)
public IEnumerable<SolutionRef> Get(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<EntityUid> input,
[CommandArgument] ValueRef<string> name
)
{
return input.Select(x => Get(x, name)).Where(x => x is not null).Cast<SolutionRef>();
return input.Select(x => Get(ctx, x, name)).Where(x => x is not null).Cast<SolutionRef>();
}
[CommandImplementation("adjreagent")]
public SolutionRef AdjReagent(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] SolutionRef input,
ProtoId<ReagentPrototype> proto,
FixedPoint2 amount
[CommandArgument] Prototype<ReagentPrototype> name,
[CommandArgument] ValueRef<FixedPoint2> amountRef
)
{
_solutionContainer ??= GetSys<SharedSolutionContainerSystem>();
var amount = amountRef.Evaluate(ctx);
if (amount > 0)
{
_solutionContainer.TryAddReagent(input.Solution, proto, amount, out _);
_solutionContainer.TryAddReagent(input.Solution, name.Value.ID, amount, out _);
}
else if (amount < 0)
{
_solutionContainer.RemoveReagent(input.Solution, proto, -amount);
_solutionContainer.RemoveReagent(input.Solution, name.Value.ID, -amount);
}
return input;
@@ -57,11 +66,12 @@ public sealed class SolutionCommand : ToolshedCommand
[CommandImplementation("adjreagent")]
public IEnumerable<SolutionRef> AdjReagent(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<SolutionRef> input,
ProtoId<ReagentPrototype> name,
FixedPoint2 amount
[CommandArgument] Prototype<ReagentPrototype> name,
[CommandArgument] ValueRef<FixedPoint2> amountRef
)
=> input.Select(x => AdjReagent(x, name, amount));
=> input.Select(x => AdjReagent(ctx, x, name, amountRef));
}
public readonly record struct SolutionRef(Entity<SolutionComponent> Solution)

View File

@@ -36,50 +36,82 @@ public sealed class TagCommand : ToolshedCommand
}
[CommandImplementation("add")]
public EntityUid Add([PipedArgument] EntityUid input, ProtoId<TagPrototype> tag)
public EntityUid Add(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] EntityUid input,
[CommandArgument] ValueRef<string, Prototype<TagPrototype>> @ref
)
{
_tag ??= GetSys<TagSystem>();
_tag.AddTag(input, tag);
_tag.AddTag(input, @ref.Evaluate(ctx)!);
return input;
}
[CommandImplementation("add")]
public IEnumerable<EntityUid> Add([PipedArgument] IEnumerable<EntityUid> input, ProtoId<TagPrototype> tag)
=> input.Select(x => Add(x, tag));
public IEnumerable<EntityUid> Add(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<EntityUid> input,
[CommandArgument] ValueRef<string, Prototype<TagPrototype>> @ref
)
=> input.Select(x => Add(ctx, x, @ref));
[CommandImplementation("rm")]
public EntityUid Rm([PipedArgument] EntityUid input, ProtoId<TagPrototype> tag)
public EntityUid Rm(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] EntityUid input,
[CommandArgument] ValueRef<string, Prototype<TagPrototype>> @ref
)
{
_tag ??= GetSys<TagSystem>();
_tag.RemoveTag(input, tag);
_tag.RemoveTag(input, @ref.Evaluate(ctx)!);
return input;
}
[CommandImplementation("rm")]
public IEnumerable<EntityUid> Rm([PipedArgument] IEnumerable<EntityUid> input, ProtoId<TagPrototype> tag)
=> input.Select(x => Rm(x, tag));
public IEnumerable<EntityUid> Rm(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<EntityUid> input,
[CommandArgument] ValueRef<string, Prototype<TagPrototype>> @ref
)
=> input.Select(x => Rm(ctx, x, @ref));
[CommandImplementation("addmany")]
public EntityUid AddMany([PipedArgument] EntityUid input, IEnumerable<ProtoId<TagPrototype>> tags)
public EntityUid AddMany(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] EntityUid input,
[CommandArgument] ValueRef<IEnumerable<string>, IEnumerable<string>> @ref
)
{
_tag ??= GetSys<TagSystem>();
_tag.AddTags(input, tags);
_tag.AddTags(input, (IEnumerable<ProtoId<TagPrototype>>)@ref.Evaluate(ctx)!);
return input;
}
[CommandImplementation("addmany")]
public IEnumerable<EntityUid> AddMany([PipedArgument] IEnumerable<EntityUid> input, IEnumerable<ProtoId<TagPrototype>> tags)
=> input.Select(x => AddMany(x, tags.ToArray()));
public IEnumerable<EntityUid> AddMany(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<EntityUid> input,
[CommandArgument] ValueRef<IEnumerable<string>, IEnumerable<string>> @ref
)
=> input.Select(x => AddMany(ctx, x, @ref));
[CommandImplementation("rmmany")]
public EntityUid RmMany([PipedArgument] EntityUid input, IEnumerable<ProtoId<TagPrototype>> tags)
public EntityUid RmMany(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] EntityUid input,
[CommandArgument] ValueRef<IEnumerable<string>, IEnumerable<string>> @ref
)
{
_tag ??= GetSys<TagSystem>();
_tag.RemoveTags(input, tags);
_tag.RemoveTags(input, (IEnumerable<ProtoId<TagPrototype>>)@ref.Evaluate(ctx)!);
return input;
}
[CommandImplementation("rmmany")]
public IEnumerable<EntityUid> RmMany([PipedArgument] IEnumerable<EntityUid> input, IEnumerable<ProtoId<TagPrototype>> tags)
=> input.Select(x => RmMany(x, tags.ToArray()));
public IEnumerable<EntityUid> RmMany(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<EntityUid> input,
[CommandArgument] ValueRef<IEnumerable<string>, IEnumerable<string>> @ref
)
=> input.Select(x => RmMany(ctx, x, @ref));
}

View File

@@ -169,13 +169,11 @@ public sealed class AmeNodeGroup : BaseNodeGroup
public float CalculatePower(int fuel, int cores)
{
// Balanced around a single core AME with injection level 2 producing 120KW.
// Two core with four injection is 150kW. Two core with two injection is 90kW.
// Overclocking yields diminishing returns until it evens out at around 360KW.
// Increasing core count creates diminishing returns, increasing injection amount increases
// Unlike the previous solution, increasing fuel and cores always leads to an increase in power, even if by very small amounts.
// Increasing core count without increasing fuel always leads to reduced power as well.
// At 18+ cores and 2 inject, the power produced is less than 0, the Max ensures the AME can never produce "negative" power.
return MathF.Max(200000f * MathF.Log10(2 * fuel * MathF.Pow(cores, (float)-0.5)), 0);
// The adjustment for cores make it so that a 1 core AME at 2 injections is better than a 2 core AME at 2 injections.
// However, for the relative amounts for each (1 core at 2 and 2 core at 4), more cores has more output.
return 200000f * MathF.Log10(fuel * fuel) * MathF.Pow(0.75f, cores - 1);
}
public int GetTotalStability()

View File

@@ -1,4 +1,3 @@
using Content.Server.Animals.Systems;
using Content.Shared.Storage;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
@@ -10,47 +9,44 @@ namespace Content.Server.Animals.Components;
/// It also grants an action to players who are controlling these entities, allowing them to do it manually.
/// </summary>
[RegisterComponent, Access(typeof(EggLayerSystem)), AutoGenerateComponentPause]
[RegisterComponent]
public sealed partial class EggLayerComponent : Component
{
/// <summary>
/// The item that gets laid/spawned, retrieved from animal prototype.
/// </summary>
[DataField(required: true)]
public List<EntitySpawnEntry> EggSpawn = new();
/// <summary>
/// Player action.
/// </summary>
[DataField]
public EntProtoId EggLayAction = "ActionAnimalLayEgg";
[DataField]
public SoundSpecifier EggLaySound = new SoundPathSpecifier("/Audio/Effects/pop.ogg");
/// <summary>
/// The amount of nutrient consumed on update.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float HungerUsage = 60f;
/// <summary>
/// Minimum cooldown used for the automatic egg laying.
/// </summary>
[DataField]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float EggLayCooldownMin = 60f;
/// <summary>
/// Maximum cooldown used for the automatic egg laying.
/// </summary>
[DataField]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float EggLayCooldownMax = 120f;
/// <summary>
/// The amount of nutrient consumed on update.
/// Set during component init.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float CurrentEggLayCooldown;
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
public List<EntitySpawnEntry> EggSpawn = default!;
[DataField]
public float HungerUsage = 60f;
public SoundSpecifier EggLaySound = new SoundPathSpecifier("/Audio/Effects/pop.ogg");
[DataField]
public float AccumulatedFrametime;
[DataField] public EntityUid? Action;
/// <summary>
/// When to next try to produce.
/// </summary>
[DataField, AutoPausedField]
public TimeSpan NextGrowth = TimeSpan.Zero;
}

View File

@@ -0,0 +1,59 @@
using Content.Server.Animals.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Animals.Components
/// <summary>
/// Lets an entity produce milk. Uses hunger if present.
/// </summary>
{
[RegisterComponent, Access(typeof(UdderSystem))]
internal sealed partial class UdderComponent : Component
{
/// <summary>
/// The reagent to produce.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
public ProtoId<ReagentPrototype> ReagentId = "Milk";
/// <summary>
/// The name of <see cref="Solution"/>.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
public string SolutionName = "udder";
/// <summary>
/// The solution to add reagent to.
/// </summary>
[DataField]
public Entity<SolutionComponent>? Solution = null;
/// <summary>
/// The amount of reagent to be generated on update.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 QuantityPerUpdate = 25;
/// <summary>
/// The amount of nutrient consumed on update.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float HungerUsage = 10f;
/// <summary>
/// How long to wait before producing.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1);
/// <summary>
/// When to next try to produce.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextGrowth = TimeSpan.FromSeconds(0);
}
}

View File

@@ -1,22 +1,23 @@
using Content.Server.Animals.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Animals;
namespace Content.Server.Animals.Components;
/// <summary>
/// Gives the ability to produce wool fibers;
/// produces endlessly if the owner does not have a HungerComponent.
/// Lets an entity produce wool fibers. Uses hunger if present.
/// </summary>
[RegisterComponent, AutoGenerateComponentState, AutoGenerateComponentPause, NetworkedComponent]
[RegisterComponent, Access(typeof(WoolySystem))]
public sealed partial class WoolyComponent : Component
{
/// <summary>
/// The reagent to grow.
/// </summary>
[DataField, AutoNetworkedField]
[DataField, ViewVariables(VVAccess.ReadOnly)]
public ProtoId<ReagentPrototype> ReagentId = "Fiber";
/// <summary>
@@ -28,30 +29,30 @@ public sealed partial class WoolyComponent : Component
/// <summary>
/// The solution to add reagent to.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
[DataField]
public Entity<SolutionComponent>? Solution;
/// <summary>
/// The amount of reagent to be generated on update.
/// </summary>
[DataField, AutoNetworkedField]
[DataField, ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 Quantity = 25;
/// <summary>
/// The amount of nutrient consumed on update.
/// </summary>
[DataField, AutoNetworkedField]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float HungerUsage = 10f;
/// <summary>
/// How long to wait before growing wool.
/// </summary>
[DataField, AutoNetworkedField]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1);
/// <summary>
/// When to next try growing wool.
/// </summary>
[DataField, AutoPausedField, Access(typeof(WoolySystem))]
public TimeSpan NextGrowth = TimeSpan.Zero;
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextGrowth = TimeSpan.FromSeconds(0);
}

View File

@@ -7,15 +7,15 @@ using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Storage;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Animals.Systems;
/// <summary>
/// Gives the ability to lay eggs/other things;
/// produces endlessly if the owner does not have a HungerComponent.
/// Gives ability to produce eggs, produces endless if the
/// owner has no HungerComponent
/// </summary>
public sealed class EggLayerSystem : EntitySystem
{
@@ -23,7 +23,6 @@ public sealed class EggLayerSystem : EntitySystem
[Dependency] private readonly ActionsSystem _actions = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly HungerSystem _hunger = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
@@ -38,6 +37,7 @@ public sealed class EggLayerSystem : EntitySystem
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<EggLayerComponent>();
while (query.MoveNext(out var uid, out var eggLayer))
{
@@ -45,17 +45,13 @@ public sealed class EggLayerSystem : EntitySystem
if (HasComp<ActorComponent>(uid))
continue;
if (_timing.CurTime < eggLayer.NextGrowth)
eggLayer.AccumulatedFrametime += frameTime;
if (eggLayer.AccumulatedFrametime < eggLayer.CurrentEggLayCooldown)
continue;
// Randomize next growth time for more organic egglaying.
eggLayer.NextGrowth += TimeSpan.FromSeconds(_random.NextFloat(eggLayer.EggLayCooldownMin, eggLayer.EggLayCooldownMax));
if (_mobState.IsDead(uid))
continue;
// Hungerlevel check/modification is done in TryLayEgg()
// so it's used for player controlled chickens as well.
eggLayer.AccumulatedFrametime -= eggLayer.CurrentEggLayCooldown;
eggLayer.CurrentEggLayCooldown = _random.NextFloat(eggLayer.EggLayCooldownMin, eggLayer.EggLayCooldownMax);
TryLayEgg(uid, eggLayer);
}
@@ -64,12 +60,11 @@ public sealed class EggLayerSystem : EntitySystem
private void OnMapInit(EntityUid uid, EggLayerComponent component, MapInitEvent args)
{
_actions.AddAction(uid, ref component.Action, component.EggLayAction);
component.NextGrowth = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(component.EggLayCooldownMin, component.EggLayCooldownMax));
component.CurrentEggLayCooldown = _random.NextFloat(component.EggLayCooldownMin, component.EggLayCooldownMax);
}
private void OnEggLayAction(EntityUid uid, EggLayerComponent egglayer, EggLayInstantActionEvent args)
{
// Cooldown is handeled by ActionAnimalLayEgg in types.yml.
args.Handled = TryLayEgg(uid, egglayer);
}
@@ -81,10 +76,10 @@ public sealed class EggLayerSystem : EntitySystem
if (_mobState.IsDead(uid))
return false;
// Allow infinitely laying eggs if they can't get hungry.
// Allow infinitely laying eggs if they can't get hungry
if (TryComp<HungerComponent>(uid, out var hunger))
{
if (_hunger.GetHunger(hunger) < egglayer.HungerUsage)
if (hunger.CurrentHunger < egglayer.HungerUsage)
{
_popup.PopupEntity(Loc.GetString("action-popup-lay-egg-too-hungry"), uid, uid);
return false;

View File

@@ -1,7 +1,8 @@
using Content.Shared.Chemistry.Components;
using Content.Server.Animals.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
@@ -11,17 +12,18 @@ using Content.Shared.Udder;
using Content.Shared.Verbs;
using Robust.Shared.Timing;
namespace Content.Shared.Animals;
namespace Content.Server.Animals.Systems;
/// <summary>
/// Gives the ability to produce milkable reagents;
/// produces endlessly if the owner does not have a HungerComponent.
/// Gives ability to produce milkable reagents, produces endless if the
/// owner has no HungerComponent
/// </summary>
public sealed class UdderSystem : EntitySystem
internal sealed class UdderSystem : EntitySystem
{
[Dependency] private readonly HungerSystem _hunger = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
@@ -29,37 +31,26 @@ public sealed class UdderSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<UdderComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<UdderComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
SubscribeLocalEvent<UdderComponent, MilkingDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<UdderComponent, ExaminedEvent>(OnExamine);
}
private void OnMapInit(EntityUid uid, UdderComponent component, MapInitEvent args)
{
component.NextGrowth = _timing.CurTime + component.GrowthDelay;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<UdderComponent>();
var now = _timing.CurTime;
while (query.MoveNext(out var uid, out var udder))
{
if (_timing.CurTime < udder.NextGrowth)
if (now < udder.NextGrowth)
continue;
udder.NextGrowth += udder.GrowthDelay;
udder.NextGrowth = now + udder.GrowthDelay;
if (_mobState.IsDead(uid))
continue;
if (!_solutionContainerSystem.ResolveSolution(uid, udder.SolutionName, ref udder.Solution, out var solution))
continue;
if (solution.AvailableVolume == 0)
continue;
// Actually there is food digestion so no problem with instant reagent generation "OnFeed"
if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger))
{
@@ -70,6 +61,9 @@ public sealed class UdderSystem : EntitySystem
_hunger.ModifyHunger(uid, -udder.HungerUsage, hunger);
}
if (!_solutionContainerSystem.ResolveSolution(uid, udder.SolutionName, ref udder.Solution))
continue;
//TODO: toxins from bloodstream !?
_solutionContainerSystem.TryAddReagent(udder.Solution.Value, udder.ReagentId, udder.QuantityPerUpdate, out _);
}
@@ -105,7 +99,7 @@ public sealed class UdderSystem : EntitySystem
var quantity = solution.Volume;
if (quantity == 0)
{
_popupSystem.PopupClient(Loc.GetString("udder-system-dry"), entity.Owner, args.Args.User);
_popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), entity.Owner, args.Args.User);
return;
}
@@ -115,7 +109,7 @@ public sealed class UdderSystem : EntitySystem
var split = _solutionContainerSystem.SplitSolution(entity.Comp.Solution.Value, quantity);
_solutionContainerSystem.TryAddSolution(targetSoln.Value, split);
_popupSystem.PopupClient(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner,
_popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner,
args.Args.User, PopupType.Medium);
}
@@ -140,50 +134,4 @@ public sealed class UdderSystem : EntitySystem
};
args.Verbs.Add(verb);
}
/// <summary>
/// Defines the text provided on examine.
/// Changes depending on the amount of hunger the target has.
/// </summary>
private void OnExamine(Entity<UdderComponent> entity, ref ExaminedEvent args)
{
var entityIdentity = Identity.Entity(args.Examined, EntityManager);
string message;
// Check if the target has hunger, otherwise return not hungry.
if (!TryComp<HungerComponent>(entity, out var hunger))
{
message = Loc.GetString("udder-system-examine-none", ("entity", entityIdentity));
args.PushMarkup(message);
return;
}
// Choose the correct examine string based on HungerThreshold.
switch (_hunger.GetHungerThreshold(hunger))
{
case >= HungerThreshold.Overfed:
message = Loc.GetString("udder-system-examine-overfed", ("entity", entityIdentity));
break;
case HungerThreshold.Okay:
message = Loc.GetString("udder-system-examine-okay", ("entity", entityIdentity));
break;
case HungerThreshold.Peckish:
message = Loc.GetString("udder-system-examine-hungry", ("entity", entityIdentity));
break;
// There's a final hunger threshold called "dead" but animals don't actually die so we'll re-use this.
case <= HungerThreshold.Starving:
message = Loc.GetString("udder-system-examine-starved", ("entity", entityIdentity));
break;
default:
return;
}
args.PushMarkup(message);
}
}

View File

@@ -1,15 +1,16 @@
using Content.Server.Animals.Components;
using Content.Server.Nutrition;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.Timing;
namespace Content.Shared.Animals;
namespace Content.Server.Animals.Systems;
/// <summary>
/// Gives ability to produce fiber reagents;
/// produces endlessly if the owner has no HungerComponent.
/// Gives ability to produce fiber reagents, produces endless if the
/// owner has no HungerComponent
/// </summary>
public sealed class WoolySystem : EntitySystem
{
@@ -23,12 +24,6 @@ public sealed class WoolySystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<WoolyComponent, BeforeFullyEatenEvent>(OnBeforeFullyEaten);
SubscribeLocalEvent<WoolyComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(EntityUid uid, WoolyComponent component, MapInitEvent args)
{
component.NextGrowth = _timing.CurTime + component.GrowthDelay;
}
public override void Update(float frameTime)
@@ -36,22 +31,17 @@ public sealed class WoolySystem : EntitySystem
base.Update(frameTime);
var query = EntityQueryEnumerator<WoolyComponent>();
var now = _timing.CurTime;
while (query.MoveNext(out var uid, out var wooly))
{
if (_timing.CurTime < wooly.NextGrowth)
if (now < wooly.NextGrowth)
continue;
wooly.NextGrowth += wooly.GrowthDelay;
wooly.NextGrowth = now + wooly.GrowthDelay;
if (_mobState.IsDead(uid))
continue;
if (!_solutionContainer.ResolveSolution(uid, wooly.SolutionName, ref wooly.Solution, out var solution))
continue;
if (solution.AvailableVolume == 0)
continue;
// Actually there is food digestion so no problem with instant reagent generation "OnFeed"
if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger))
{
@@ -62,6 +52,9 @@ public sealed class WoolySystem : EntitySystem
_hunger.ModifyHunger(uid, -wooly.HungerUsage, hunger);
}
if (!_solutionContainer.ResolveSolution(uid, wooly.SolutionName, ref wooly.Solution))
continue;
_solutionContainer.TryAddReagent(wooly.Solution.Value, wooly.ReagentId, wooly.Quantity, out _);
}
}

View File

@@ -186,11 +186,6 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
if (args.NewMobState != MobState.Dead)
return;
var ev = new BeforeRemoveAnomalyOnDeathEvent();
RaiseLocalEvent(args.Target, ref ev);
if (ev.Cancelled)
return;
_anomaly.ChangeAnomalyHealth(ent, -2); //Shutdown it
}

View File

@@ -1,542 +0,0 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.DeviceNetwork.Components;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Consoles;
using Content.Shared.Labels.Components;
using Content.Shared.Pinpointer;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Content.Server.Atmos.Consoles;
public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleSystem
{
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly SharedMapSystem _sharedMapSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
// Private variables
// Note: this data does not need to be saved
private Dictionary<EntityUid, Dictionary<Vector2i, AtmosPipeChunk>> _gridAtmosPipeChunks = new();
private float _updateTimer = 1.0f;
// Constants
private const float UpdateTime = 1.0f;
private const int ChunkSize = 4;
public override void Initialize()
{
base.Initialize();
// Console events
SubscribeLocalEvent<AtmosMonitoringConsoleComponent, ComponentInit>(OnConsoleInit);
SubscribeLocalEvent<AtmosMonitoringConsoleComponent, AnchorStateChangedEvent>(OnConsoleAnchorChanged);
SubscribeLocalEvent<AtmosMonitoringConsoleComponent, EntParentChangedMessage>(OnConsoleParentChanged);
// Tracked device events
SubscribeLocalEvent<AtmosMonitoringConsoleDeviceComponent, NodeGroupsRebuilt>(OnEntityNodeGroupsRebuilt);
SubscribeLocalEvent<AtmosMonitoringConsoleDeviceComponent, AtmosPipeColorChangedEvent>(OnEntityPipeColorChanged);
SubscribeLocalEvent<AtmosMonitoringConsoleDeviceComponent, EntityTerminatingEvent>(OnEntityShutdown);
// Grid events
SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
}
#region Event handling
private void OnConsoleInit(EntityUid uid, AtmosMonitoringConsoleComponent component, ComponentInit args)
{
InitializeAtmosMonitoringConsole(uid, component);
}
private void OnConsoleAnchorChanged(EntityUid uid, AtmosMonitoringConsoleComponent component, AnchorStateChangedEvent args)
{
InitializeAtmosMonitoringConsole(uid, component);
}
private void OnConsoleParentChanged(EntityUid uid, AtmosMonitoringConsoleComponent component, EntParentChangedMessage args)
{
component.ForceFullUpdate = true;
InitializeAtmosMonitoringConsole(uid, component);
}
private void OnEntityNodeGroupsRebuilt(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component, NodeGroupsRebuilt args)
{
InitializeAtmosMonitoringDevice(uid, component);
}
private void OnEntityPipeColorChanged(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component, AtmosPipeColorChangedEvent args)
{
InitializeAtmosMonitoringDevice(uid, component);
}
private void OnEntityShutdown(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component, EntityTerminatingEvent args)
{
ShutDownAtmosMonitoringEntity(uid, component);
}
private void OnGridSplit(ref GridSplitEvent args)
{
// Collect grids
var allGrids = args.NewGrids.ToList();
if (!allGrids.Contains(args.Grid))
allGrids.Add(args.Grid);
// Rebuild the pipe networks on the affected grids
foreach (var ent in allGrids)
{
if (!TryComp<MapGridComponent>(ent, out var grid))
continue;
RebuildAtmosPipeGrid(ent, grid);
}
// Update atmos monitoring consoles that stand upon an updated grid
var query = AllEntityQuery<AtmosMonitoringConsoleComponent, TransformComponent>();
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
{
if (entXform.GridUid == null)
continue;
if (!allGrids.Contains(entXform.GridUid.Value))
continue;
InitializeAtmosMonitoringConsole(ent, entConsole);
}
}
#endregion
#region UI updates
public override void Update(float frameTime)
{
base.Update(frameTime);
_updateTimer += frameTime;
if (_updateTimer >= UpdateTime)
{
_updateTimer -= UpdateTime;
var query = AllEntityQuery<AtmosMonitoringConsoleComponent, TransformComponent>();
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
{
if (entXform?.GridUid == null)
continue;
UpdateUIState(ent, entConsole, entXform);
}
}
}
public void UpdateUIState
(EntityUid uid,
AtmosMonitoringConsoleComponent component,
TransformComponent xform)
{
if (!_userInterfaceSystem.IsUiOpen(uid, AtmosMonitoringConsoleUiKey.Key))
return;
var gridUid = xform.GridUid!.Value;
if (!TryComp<MapGridComponent>(gridUid, out var mapGrid))
return;
if (!TryComp<GridAtmosphereComponent>(gridUid, out var atmosphere))
return;
// The grid must have a NavMapComponent to visualize the map in the UI
EnsureComp<NavMapComponent>(gridUid);
// Gathering data to be send to the client
var atmosNetworks = new List<AtmosMonitoringConsoleEntry>();
var query = AllEntityQuery<GasPipeSensorComponent, TransformComponent>();
while (query.MoveNext(out var ent, out var entSensor, out var entXform))
{
if (entXform?.GridUid != xform.GridUid)
continue;
if (!entXform.Anchored)
continue;
var entry = CreateAtmosMonitoringConsoleEntry(ent, entXform);
if (entry != null)
atmosNetworks.Add(entry.Value);
}
// Set the UI state
_userInterfaceSystem.SetUiState(uid, AtmosMonitoringConsoleUiKey.Key,
new AtmosMonitoringConsoleBoundInterfaceState(atmosNetworks.ToArray()));
}
private AtmosMonitoringConsoleEntry? CreateAtmosMonitoringConsoleEntry(EntityUid uid, TransformComponent xform)
{
AtmosMonitoringConsoleEntry? entry = null;
var netEnt = GetNetEntity(uid);
var name = MetaData(uid).EntityName;
var address = string.Empty;
if (xform.GridUid == null)
return null;
if (!TryGettingFirstPipeNode(uid, out var pipeNode, out var netId) ||
pipeNode == null ||
netId == null)
return null;
var pipeColor = TryComp<AtmosPipeColorComponent>(uid, out var colorComponent) ? colorComponent.Color : Color.White;
// Name the entity based on its label, if available
if (TryComp<LabelComponent>(uid, out var label) && label.CurrentLabel != null)
name = label.CurrentLabel;
// Otherwise use its base name and network address
else if (TryComp<DeviceNetworkComponent>(uid, out var deviceNet))
address = deviceNet.Address;
// Entry for unpowered devices
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcPowerReceiver) && !apcPowerReceiver.Powered)
{
entry = new AtmosMonitoringConsoleEntry(netEnt, GetNetCoordinates(xform.Coordinates), netId.Value, name, address)
{
IsPowered = false,
Color = pipeColor
};
return entry;
}
// Entry for powered devices
var gasData = new Dictionary<Gas, float>();
var isAirPresent = pipeNode.Air.TotalMoles > 0;
if (isAirPresent)
{
foreach (var gas in Enum.GetValues<Gas>())
{
if (pipeNode.Air[(int)gas] > 0)
gasData.Add(gas, pipeNode.Air[(int)gas] / pipeNode.Air.TotalMoles);
}
}
entry = new AtmosMonitoringConsoleEntry(netEnt, GetNetCoordinates(xform.Coordinates), netId.Value, name, address)
{
TemperatureData = isAirPresent ? pipeNode.Air.Temperature : 0f,
PressureData = pipeNode.Air.Pressure,
TotalMolData = pipeNode.Air.TotalMoles,
GasData = gasData,
Color = pipeColor
};
return entry;
}
private Dictionary<NetEntity, AtmosDeviceNavMapData> GetAllAtmosDeviceNavMapData(EntityUid gridUid)
{
var atmosDeviceNavMapData = new Dictionary<NetEntity, AtmosDeviceNavMapData>();
var query = AllEntityQuery<AtmosMonitoringConsoleDeviceComponent, TransformComponent>();
while (query.MoveNext(out var ent, out var entComponent, out var entXform))
{
if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, gridUid, out var data))
atmosDeviceNavMapData.Add(data.Value.NetEntity, data.Value);
}
return atmosDeviceNavMapData;
}
private bool TryGetAtmosDeviceNavMapData
(EntityUid uid,
AtmosMonitoringConsoleDeviceComponent component,
TransformComponent xform,
EntityUid gridUid,
[NotNullWhen(true)] out AtmosDeviceNavMapData? device)
{
device = null;
if (component.NavMapBlip == null)
return false;
if (xform.GridUid != gridUid)
return false;
if (!xform.Anchored)
return false;
var direction = xform.LocalRotation.GetCardinalDir();
if (!TryGettingFirstPipeNode(uid, out var _, out var netId))
netId = -1;
var color = Color.White;
if (TryComp<AtmosPipeColorComponent>(uid, out var atmosPipeColor))
color = atmosPipeColor.Color;
device = new AtmosDeviceNavMapData(GetNetEntity(uid), GetNetCoordinates(xform.Coordinates), netId.Value, component.NavMapBlip.Value, direction, color);
return true;
}
#endregion
#region Pipe net functions
private void RebuildAtmosPipeGrid(EntityUid gridUid, MapGridComponent grid)
{
var allChunks = new Dictionary<Vector2i, AtmosPipeChunk>();
// Adds all atmos pipes to the nav map via bit mask chunks
var queryPipes = AllEntityQuery<AtmosPipeColorComponent, NodeContainerComponent, TransformComponent>();
while (queryPipes.MoveNext(out var ent, out var entAtmosPipeColor, out var entNodeContainer, out var entXform))
{
if (entXform.GridUid != gridUid)
continue;
if (!entXform.Anchored)
continue;
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates);
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize);
var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize);
if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
{
chunk = new AtmosPipeChunk(chunkOrigin);
allChunks[chunkOrigin] = chunk;
}
UpdateAtmosPipeChunk(ent, entNodeContainer, entAtmosPipeColor, GetTileIndex(relative), ref chunk);
}
// Add or update the chunks on the associated grid
_gridAtmosPipeChunks[gridUid] = allChunks;
// Update the consoles that are on the same grid
var queryConsoles = AllEntityQuery<AtmosMonitoringConsoleComponent, TransformComponent>();
while (queryConsoles.MoveNext(out var ent, out var entConsole, out var entXform))
{
if (gridUid != entXform.GridUid)
continue;
entConsole.AtmosPipeChunks = allChunks;
Dirty(ent, entConsole);
}
}
private void RebuildSingleTileOfPipeNetwork(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords)
{
if (!_gridAtmosPipeChunks.TryGetValue(gridUid, out var allChunks))
allChunks = new Dictionary<Vector2i, AtmosPipeChunk>();
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, coords);
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize);
var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize);
var tileIdx = GetTileIndex(relative);
if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
chunk = new AtmosPipeChunk(chunkOrigin);
// Remove all stale values for the tile
foreach (var (index, atmosPipeData) in chunk.AtmosPipeData)
{
var mask = (ulong)SharedNavMapSystem.AllDirMask << tileIdx * SharedNavMapSystem.Directions;
chunk.AtmosPipeData[index] = atmosPipeData & ~mask;
}
// Rebuild the tile's pipe data
foreach (var ent in _sharedMapSystem.GetAnchoredEntities(gridUid, grid, coords))
{
if (!TryComp<AtmosPipeColorComponent>(ent, out var entAtmosPipeColor))
continue;
if (!TryComp<NodeContainerComponent>(ent, out var entNodeContainer))
continue;
UpdateAtmosPipeChunk(ent, entNodeContainer, entAtmosPipeColor, tileIdx, ref chunk);
}
// Add or update the chunk on the associated grid
// Only the modified chunk will be sent to the client
chunk.LastUpdate = _gameTiming.CurTick;
allChunks[chunkOrigin] = chunk;
_gridAtmosPipeChunks[gridUid] = allChunks;
// Update the components of the monitoring consoles that are attached to the same grid
var query = AllEntityQuery<AtmosMonitoringConsoleComponent, TransformComponent>();
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
{
if (gridUid != entXform.GridUid)
continue;
entConsole.AtmosPipeChunks = allChunks;
Dirty(ent, entConsole);
}
}
private void UpdateAtmosPipeChunk(EntityUid uid, NodeContainerComponent nodeContainer, AtmosPipeColorComponent pipeColor, int tileIdx, ref AtmosPipeChunk chunk)
{
// Entities that are actively being deleted are not to be drawn
if (MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating)
return;
foreach ((var id, var node) in nodeContainer.Nodes)
{
if (node is not PipeNode)
continue;
var pipeNode = (PipeNode)node;
var netId = GetPipeNodeNetId(pipeNode);
var pipeDirection = pipeNode.CurrentPipeDirection;
chunk.AtmosPipeData.TryGetValue((netId, pipeColor.Color.ToHex()), out var atmosPipeData);
atmosPipeData |= (ulong)pipeDirection << tileIdx * SharedNavMapSystem.Directions;
chunk.AtmosPipeData[(netId, pipeColor.Color.ToHex())] = atmosPipeData;
}
}
private bool TryGettingFirstPipeNode(EntityUid uid, [NotNullWhen(true)] out PipeNode? pipeNode, [NotNullWhen(true)] out int? netId)
{
pipeNode = null;
netId = null;
if (!TryComp<NodeContainerComponent>(uid, out var nodeContainer))
return false;
foreach (var node in nodeContainer.Nodes.Values)
{
if (node is PipeNode)
{
pipeNode = (PipeNode)node;
netId = GetPipeNodeNetId(pipeNode);
return true;
}
}
return false;
}
private int GetPipeNodeNetId(PipeNode pipeNode)
{
if (pipeNode.NodeGroup is BaseNodeGroup)
{
var nodeGroup = (BaseNodeGroup)pipeNode.NodeGroup;
return nodeGroup.NetId;
}
return -1;
}
#endregion
#region Initialization functions
private void InitializeAtmosMonitoringConsole(EntityUid uid, AtmosMonitoringConsoleComponent component)
{
var xform = Transform(uid);
if (xform.GridUid == null)
return;
var grid = xform.GridUid.Value;
if (!TryComp<MapGridComponent>(grid, out var map))
return;
component.AtmosDevices = GetAllAtmosDeviceNavMapData(grid);
if (!_gridAtmosPipeChunks.TryGetValue(grid, out var chunks))
{
RebuildAtmosPipeGrid(grid, map);
}
else
{
component.AtmosPipeChunks = chunks;
Dirty(uid, component);
}
}
private void InitializeAtmosMonitoringDevice(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component)
{
// Rebuild tile
var xform = Transform(uid);
var gridUid = xform.GridUid;
if (gridUid != null && TryComp<MapGridComponent>(gridUid, out var grid))
RebuildSingleTileOfPipeNetwork(gridUid.Value, grid, xform.Coordinates);
// Update blips on affected consoles
if (component.NavMapBlip == null)
return;
var netEntity = EntityManager.GetNetEntity(uid);
var query = AllEntityQuery<AtmosMonitoringConsoleComponent, TransformComponent>();
while (query.MoveNext(out var ent, out var entConsole, out var entXform))
{
var isDirty = entConsole.AtmosDevices.Remove(netEntity);
if (gridUid != null &&
gridUid == entXform.GridUid &&
xform.Anchored &&
TryGetAtmosDeviceNavMapData(uid, component, xform, gridUid.Value, out var data))
{
entConsole.AtmosDevices.Add(netEntity, data.Value);
isDirty = true;
}
if (isDirty)
Dirty(ent, entConsole);
}
}
private void ShutDownAtmosMonitoringEntity(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component)
{
// Rebuild tile
var xform = Transform(uid);
var gridUid = xform.GridUid;
if (gridUid != null && TryComp<MapGridComponent>(gridUid, out var grid))
RebuildSingleTileOfPipeNetwork(gridUid.Value, grid, xform.Coordinates);
// Update blips on affected consoles
if (component.NavMapBlip == null)
return;
var netEntity = EntityManager.GetNetEntity(uid);
var query = AllEntityQuery<AtmosMonitoringConsoleComponent>();
while (query.MoveNext(out var ent, out var entConsole))
{
if (entConsole.AtmosDevices.Remove(netEntity))
Dirty(ent, entConsole);
}
}
#endregion
private int GetTileIndex(Vector2i relativeTile)
{
return relativeTile.X * ChunkSize + relativeTile.Y;
}
}

View File

@@ -11,7 +11,6 @@ using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.DeviceNetwork;
using Content.Shared.Power;
using Content.Shared.Tag;

View File

@@ -0,0 +1,31 @@
using Content.Shared.Atmos;
namespace Content.Server.Atmos.Piping.Binary.Components
{
[RegisterComponent]
public sealed partial class GasPressurePumpComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
[DataField("enabled")]
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inlet")]
public string InletName { get; set; } = "inlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")]
public string OutletName { get; set; } = "outlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("targetPressure")]
public float TargetPressure { get; set; } = Atmospherics.OneAtmosphere;
/// <summary>
/// Max pressure of the target gas (NOT relative to source).
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maxTargetPressure")]
public float MaxTargetPressure = Atmospherics.MaxOutputPressure;
}
}

View File

@@ -1,57 +1,169 @@
using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Audio;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Power;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems;
[UsedImplicitly]
public sealed class GasPressurePumpSystem : SharedGasPressurePumpSystem
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
public override void Initialize()
[UsedImplicitly]
public sealed class GasPressurePumpSystem : EntitySystem
{
base.Initialize();
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceUpdateEvent>(OnPumpUpdated);
}
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceUpdateEvent args)
{
if (!pump.Enabled
|| (TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
|| !_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
public override void Initialize()
{
_ambientSoundSystem.SetAmbience(uid, false);
return;
base.Initialize();
SubscribeLocalEvent<GasPressurePumpComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceUpdateEvent>(OnPumpUpdated);
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceDisabledEvent>(OnPumpLeaveAtmosphere);
SubscribeLocalEvent<GasPressurePumpComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<GasPressurePumpComponent, ActivateInWorldEvent>(OnPumpActivate);
SubscribeLocalEvent<GasPressurePumpComponent, PowerChangedEvent>(OnPowerChanged);
// Bound UI subscriptions
SubscribeLocalEvent<GasPressurePumpComponent, GasPressurePumpChangeOutputPressureMessage>(OnOutputPressureChangeMessage);
SubscribeLocalEvent<GasPressurePumpComponent, GasPressurePumpToggleStatusMessage>(OnToggleStatusMessage);
}
var outputStartingPressure = outlet.Air.Pressure;
if (outputStartingPressure >= pump.TargetPressure)
private void OnInit(EntityUid uid, GasPressurePumpComponent pump, ComponentInit args)
{
_ambientSoundSystem.SetAmbience(uid, false);
return; // No need to pump gas if target has been reached.
UpdateAppearance(uid, pump);
}
if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
private void OnExamined(EntityUid uid, GasPressurePumpComponent pump, ExaminedEvent args)
{
// We calculate the necessary moles to transfer using our good ol' friend PV=nRT.
var pressureDelta = pump.TargetPressure - outputStartingPressure;
var transferMoles = (pressureDelta * outlet.Air.Volume) / (inlet.Air.Temperature * Atmospherics.R);
if (!EntityManager.GetComponent<TransformComponent>(uid).Anchored || !args.IsInDetailsRange) // Not anchored? Out of range? No status.
return;
var removed = inlet.Air.Remove(transferMoles);
_atmosphereSystem.Merge(outlet.Air, removed);
_ambientSoundSystem.SetAmbience(uid, removed.TotalMoles > 0f);
if (Loc.TryGetString("gas-pressure-pump-system-examined", out var str,
("statusColor", "lightblue"), // TODO: change with pressure?
("pressure", pump.TargetPressure)
))
{
args.PushMarkup(str);
}
}
private void OnPowerChanged(EntityUid uid, GasPressurePumpComponent component, ref PowerChangedEvent args)
{
UpdateAppearance(uid, component);
}
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceUpdateEvent args)
{
if (!pump.Enabled
|| (TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
|| !_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
{
_ambientSoundSystem.SetAmbience(uid, false);
return;
}
var outputStartingPressure = outlet.Air.Pressure;
if (outputStartingPressure >= pump.TargetPressure)
{
_ambientSoundSystem.SetAmbience(uid, false);
return; // No need to pump gas if target has been reached.
}
if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
{
// We calculate the necessary moles to transfer using our good ol' friend PV=nRT.
var pressureDelta = pump.TargetPressure - outputStartingPressure;
var transferMoles = (pressureDelta * outlet.Air.Volume) / (inlet.Air.Temperature * Atmospherics.R);
var removed = inlet.Air.Remove(transferMoles);
_atmosphereSystem.Merge(outlet.Air, removed);
_ambientSoundSystem.SetAmbience(uid, removed.TotalMoles > 0f);
}
}
private void OnPumpLeaveAtmosphere(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceDisabledEvent args)
{
pump.Enabled = false;
UpdateAppearance(uid, pump);
DirtyUI(uid, pump);
_userInterfaceSystem.CloseUi(uid, GasPressurePumpUiKey.Key);
}
private void OnPumpActivate(EntityUid uid, GasPressurePumpComponent pump, ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex)
return;
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
if (Transform(uid).Anchored)
{
_userInterfaceSystem.OpenUi(uid, GasPressurePumpUiKey.Key, actor.PlayerSession);
DirtyUI(uid, pump);
}
else
{
_popup.PopupCursor(Loc.GetString("comp-gas-pump-ui-needs-anchor"), args.User);
}
args.Handled = true;
}
private void OnToggleStatusMessage(EntityUid uid, GasPressurePumpComponent pump, GasPressurePumpToggleStatusMessage args)
{
pump.Enabled = args.Enabled;
_adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium,
$"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}");
DirtyUI(uid, pump);
UpdateAppearance(uid, pump);
}
private void OnOutputPressureChangeMessage(EntityUid uid, GasPressurePumpComponent pump, GasPressurePumpChangeOutputPressureMessage args)
{
pump.TargetPressure = Math.Clamp(args.Pressure, 0f, Atmospherics.MaxOutputPressure);
_adminLogger.Add(LogType.AtmosPressureChanged, LogImpact.Medium,
$"{ToPrettyString(args.Actor):player} set the pressure on {ToPrettyString(uid):device} to {args.Pressure}kPa");
DirtyUI(uid, pump);
}
private void DirtyUI(EntityUid uid, GasPressurePumpComponent? pump)
{
if (!Resolve(uid, ref pump))
return;
_userInterfaceSystem.SetUiState(uid, GasPressurePumpUiKey.Key,
new GasPressurePumpBoundUserInterfaceState(EntityManager.GetComponent<MetaDataComponent>(uid).EntityName, pump.TargetPressure, pump.Enabled));
}
private void UpdateAppearance(EntityUid uid, GasPressurePumpComponent? pump = null, AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref pump, ref appearance, false))
return;
bool pumpOn = pump.Enabled && (TryComp<ApcPowerReceiverComponent>(uid, out var power) && power.Powered);
_appearance.SetData(uid, PumpVisuals.Enabled, pumpOn, appearance);
}
}
}

View File

@@ -6,7 +6,6 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Audio;
using Content.Shared.Examine;
using JetBrains.Annotations;

View File

@@ -10,7 +10,6 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Visuals;
using Content.Shared.Audio;
using Content.Shared.Database;

View File

@@ -67,3 +67,15 @@ public readonly struct AtmosDeviceUpdateEvent(float dt, Entity<GridAtmosphereCom
/// </summary>
public readonly Entity<MapAtmosphereComponent?>? Map = map;
}
/// <summary>
/// Raised directed on an atmos device when it is enabled.
/// </summary>
[ByRefEvent]
public record struct AtmosDeviceEnabledEvent;
/// <summary>
/// Raised directed on an atmos device when it is enabled.
/// </summary>
[ByRefEvent]
public record struct AtmosDeviceDisabledEvent;

View File

@@ -1,24 +1,19 @@
using Content.Server.Atmos.Piping.EntitySystems;
using JetBrains.Annotations;
namespace Content.Server.Atmos.Piping.Components;
[RegisterComponent]
public sealed partial class AtmosPipeColorComponent : Component
namespace Content.Server.Atmos.Piping.Components
{
[DataField]
public Color Color { get; set; } = Color.White;
[ViewVariables(VVAccess.ReadWrite), UsedImplicitly]
public Color ColorVV
[RegisterComponent]
public sealed partial class AtmosPipeColorComponent : Component
{
get => Color;
set => IoCManager.Resolve<IEntityManager>().System<AtmosPipeColorSystem>().SetColor(Owner, this, value);
[DataField("color")]
public Color Color { get; set; } = Color.White;
[ViewVariables(VVAccess.ReadWrite), UsedImplicitly]
public Color ColorVV
{
get => Color;
set => IoCManager.Resolve<IEntityManager>().System<AtmosPipeColorSystem>().SetColor(Owner, this, value);
}
}
}
[ByRefEvent]
public record struct AtmosPipeColorChangedEvent(Color color)
{
public Color Color = color;
}

View File

@@ -1,7 +1,6 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Components;
using JetBrains.Annotations;
using Robust.Shared.Timing;
using Robust.Shared.Utility;

View File

@@ -40,9 +40,6 @@ namespace Content.Server.Atmos.Piping.EntitySystems
return;
_appearance.SetData(uid, PipeColorVisuals.Color, color, appearance);
var ev = new AtmosPipeColorChangedEvent(color);
RaiseLocalEvent(uid, ref ev);
}
}
}

View File

@@ -1,7 +1,6 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Popups;
using Content.Shared.Atmos;
@@ -9,6 +8,7 @@ using Content.Shared.Construction.Components;
using Content.Shared.Destructible;
using Content.Shared.Popups;
using JetBrains.Annotations;
using Robust.Shared.Player;
namespace Content.Server.Atmos.Piping.EntitySystems
{
@@ -16,12 +16,11 @@ namespace Content.Server.Atmos.Piping.EntitySystems
public sealed class AtmosUnsafeUnanchorSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly NodeGroupSystem _group = default!;
[Dependency] private readonly PopupSystem _popup = default!;
public override void Initialize()
{
SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, UserUnanchoredEvent>(OnUserUnanchored);
SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, BeforeUnanchoredEvent>(OnBeforeUnanchored);
SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
SubscribeLocalEvent<AtmosUnsafeUnanchorComponent, BreakageEventArgs>(OnBreak);
}
@@ -49,22 +48,15 @@ namespace Content.Server.Atmos.Piping.EntitySystems
}
}
// When unanchoring a pipe, leak the gas that was inside the pipe element.
// At this point the pipe has been scheduled to be removed from the group, but that won't happen until the next Update() call in NodeGroupSystem,
// so we have to force an update.
// This way the gas inside other connected pipes stays unchanged, while the removed pipe is completely emptied.
private void OnUserUnanchored(EntityUid uid, AtmosUnsafeUnanchorComponent component, UserUnanchoredEvent args)
private void OnBeforeUnanchored(EntityUid uid, AtmosUnsafeUnanchorComponent component, BeforeUnanchoredEvent args)
{
if (component.Enabled)
{
_group.ForceUpdate();
LeakGas(uid);
}
}
private void OnBreak(EntityUid uid, AtmosUnsafeUnanchorComponent component, BreakageEventArgs args)
{
LeakGas(uid, false);
LeakGas(uid);
// Can't use DoActsBehavior["Destruction"] in the same trigger because that would prevent us
// from leaking. So we make up for this by queueing deletion here.
QueueDel(uid);
@@ -72,17 +64,32 @@ namespace Content.Server.Atmos.Piping.EntitySystems
/// <summary>
/// Leak gas from the uid's NodeContainer into the tile atmosphere.
/// Setting removeFromPipe to false will duplicate the gas inside the pipe intead of moving it.
/// This is needed to properly handle the gas in the pipe getting deleted with the pipe.
/// </summary>
public void LeakGas(EntityUid uid, bool removeFromPipe = true)
public void LeakGas(EntityUid uid)
{
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodes))
return;
if (_atmosphere.GetContainingMixture(uid, true, true) is not { } environment)
if (_atmosphere.GetContainingMixture(uid, true, true) is not {} environment)
environment = GasMixture.SpaceGas;
var lost = 0f;
var timesLost = 0;
foreach (var node in nodes.Nodes.Values)
{
if (node is not PipeNode pipe)
continue;
var difference = pipe.Air.Pressure - environment.Pressure;
lost += Math.Min(
pipe.Volume / pipe.Air.Volume * pipe.Air.TotalMoles,
difference * environment.Volume / (environment.Temperature * Atmospherics.R)
);
timesLost++;
}
var sharedLoss = lost / timesLost;
var buffer = new GasMixture();
foreach (var node in nodes.Nodes.Values)
@@ -90,13 +97,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
if (node is not PipeNode pipe)
continue;
if (removeFromPipe)
_atmosphere.Merge(buffer, pipe.Air.RemoveVolume(pipe.Volume));
else
{
var copy = new GasMixture(pipe.Air); //clone, then remove to keep the original untouched
_atmosphere.Merge(buffer, copy.RemoveVolume(pipe.Volume));
}
_atmosphere.Merge(buffer, pipe.Air.Remove(sharedLoss));
}
_atmosphere.Merge(environment, buffer);

View File

@@ -7,7 +7,6 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Audio;
using Content.Shared.Database;

View File

@@ -7,7 +7,6 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Audio;
using Content.Shared.Database;

Some files were not shown because too many files have changed in this diff Show More