diff --git a/.envrc b/.envrc index d2ab6182d8b..81220ee609f 100644 --- a/.envrc +++ b/.envrc @@ -1,4 +1,5 @@ +set -e if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then - source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM=" + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM=" fi -use nix +use nix \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a30829a5dee..85545ac8a70 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,97 @@ -# Last match in file takes precedence. +# hasn't specified or all +* @sleepyyapril @VMSolidus @Remuchi -# TODO +/.* @DEATHB4DEFEAT +*.sln @DEATHB4DEFEAT +*.csproj @DEATHB4DEFEAT +*.dotsettings @DEATHB4DEFEAT +*.DotSettings @DEATHB4DEFEAT +*.toml @DEATHB4DEFEAT +/Content.*/IoC @DEATHB4DEFEAT + + +# Nix +*.nix @DEATHB4DEFEAT @stellar-novas +/flake.lock @DEATHB4DEFEAT @stellar-novas +/.envrc @DEATHB4DEFEAT @stellar-novas + + +# UI +*.xaml @DEATHB4DEFEAT +*.xaml.cs @DEATHB4DEFEAT + +# Lobby +/Content.Client/Lobby @DEATHB4DEFEAT +/Content.Client/MainMenu @DEATHB4DEFEAT + +# Queue +/Content.*/DiscordAuth @DEATHB4DEFEAT +/Content.*/JoinQueue @DEATHB4DEFEAT + + +# Writing +*.xml @DEATHB4DEFEAT +*.ftl @DEATHB4DEFEAT +*.md @DEATHB4DEFEAT +*.txt @DEATHB4DEFEAT + +# Shaders +*.swsl @DEATHB4DEFEAT +/Resources/Prototypes/Shaders @DEATHB4DEFEAT + +# Overlays +/Content.Client/Overlays @DEATHB4DEFEAT + +# Paint +/Content.*/Paint @DEATHB4DEFEAT + + +# Parkstation/etc +**/SimpleStation14 @DEATHB4DEFEAT +**/Parkstation @DEATHB4DEFEAT + +# Announcer system +/Content.*/Announcements @DEATHB4DEFEAT +/Content.Server/StationEvents @DEATHB4DEFEAT + +# SSD +/Content.*/SSDIndicator @DEATHB4DEFEAT + +# Station Goals +/Content.Server/StationGoal @DEATHB4DEFEAT + +# Random Bark +/Content.Server/Speech/Components/RandomBarkComponent.cs @DEATHB4DEFEAT +/Content.Server/Speech/Systems/RandomBarkSystem.cs @DEATHB4DEFEAT +/Resources/Locale/en-US/random-barks @DEATHB4DEFEAT + +# Punpun +/Content.Server/Punpun @DEATHB4DEFEAT + + +# Database +/Content.*/Database @DEATHB4DEFEAT +/Content.*/.Database @DEATHB4DEFEAT + +# Preferences +/Content.*/Preferences @DEATHB4DEFEAT +**/*CVar*/*.cs @DEATHB4DEFEAT + +# Discord +/Content.*/Discord* @DEATHB4DEFEAT + +# Loadouts +/Resources/Prototypes/Loadouts @angelofallars +/Resources/Prototypes/CharacterItemGroups @angelofallars +/Resources/Locale/en-US/loadouts @angelofallars + +# Traits +/Resources/Prototypes/Traits @angelofallars +/Resources/Locale/en-US/traits @angelofallars + +# Throwing +/Content.*/DamageOtherOnHit @angelofallars +/Content.*/Embed @angelofallars +/Content.*/ProjectileSystem @angelofallars +/Content.*/ThrownItem @angelofallars +/Content.*/ThrowEvent @angelofallars diff --git a/.github/workflows/no-submodule-update.yml b/.github/workflows/no-submodule-update.yml index f2a9d72e375..c955f35252b 100644 --- a/.github/workflows/no-submodule-update.yml +++ b/.github/workflows/no-submodule-update.yml @@ -7,6 +7,7 @@ on: jobs: this_aint_right: + if: github.actor != 'sleepyyapril' && github.actor != 'VMSolidus' && github.actor != 'DEATHB4DEFEAT' name: Submodule update in pr found runs-on: ubuntu-latest steps: diff --git a/BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns b/BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns index cba0912b0f4..b49fea90eaf 100644 Binary files a/BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns and b/BuildFiles/Mac/Space Station 14.app/Contents/Resources/ss14.icns differ diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs index b0d0365ef6b..3cc3a5f459b 100644 --- a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs +++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs @@ -31,19 +31,6 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer [AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state", }; - private Dictionary _gasShorthands = new Dictionary() - { - [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); @@ -160,15 +147,12 @@ public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlert // Add an entry for each gas foreach ((var gas, (var mol, var percent, var alert)) in gasData) { - var gasPercent = (FixedPoint2)0f; - gasPercent = percent * 100f; - - if (!_gasShorthands.TryGetValue(gas, out var gasShorthand)) - gasShorthand = "X"; + FixedPoint2 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", gasShorthand), ("value", gasPercent)), + Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasAbbreviation), ("value", gasPercent)), FontOverride = normalFont, FontColorOverride = GetAlarmStateColor(alert), HorizontalAlignment = HAlignment.Center, diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleBoundUserInterface.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..563122f962c --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleBoundUserInterface.cs @@ -0,0 +1,40 @@ +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(Owner, out var xform); + _menu?.UpdateUI(xform?.Coordinates, castState.AtmosNetworks); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Dispose(); + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleNavMapControl.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleNavMapControl.cs new file mode 100644 index 00000000000..c23ebb64355 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleNavMapControl.cs @@ -0,0 +1,295 @@ +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 _atmosPipeNetwork = new(); + private Dictionary _sRGBLookUp = new Dictionary(); + + // Look up tables for merging continuous lines. Indexed by line color + private Dictionary> _horizLines = new(); + private Dictionary> _horizLinesReversed = new(); + private Dictionary> _vertLines = new(); + private Dictionary> _vertLinesReversed = new(); + + public AtmosMonitoringConsoleNavMapControl() : base() + { + PostWallDrawingAction += DrawAllPipeNetworks; + } + + protected override void UpdateNavMap() + { + base.UpdateNavMap(); + + if (!_entManager.TryGetComponent(Owner, out var console)) + return; + + if (!_entManager.TryGetComponent(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 atmosPipeNetwork) + { + var offset = GetOffset(); + offset = offset with { Y = -offset.Y }; + + if (WorldRange / WorldMaxRange > 0.5f) + { + var pipeNetworks = new Dictionary>(); + + 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(); + + 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>(); + + 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(); + + 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 GetDecodedAtmosPipeChunks(Dictionary? chunks, MapGridComponent? grid) + { + var decodedOutput = new List(); + + 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(); + + 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; + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs new file mode 100644 index 00000000000..bfbb05d2ab1 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs @@ -0,0 +1,69 @@ +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(OnHandleState); + } + + private void OnHandleState(EntityUid uid, AtmosMonitoringConsoleComponent component, ref ComponentHandleState args) + { + Dictionary> modifiedChunks; + Dictionary 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; + } + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml new file mode 100644 index 00000000000..b6fde7592fd --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml.cs new file mode 100644 index 00000000000..515f91790f4 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml.cs @@ -0,0 +1,455 @@ +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 _navMapConsoleProtoId = "NavMapConsole"; + private ProtoId _gasPipeSensorProtoId = "GasPipeSensor"; + + public AtmosMonitoringConsoleWindow(AtmosMonitoringConsoleBoundUserInterface userInterface, EntityUid? owner) + { + RobustXamlLoader.Load(this); + _entManager = IoCManager.Resolve(); + _protoManager = IoCManager.Resolve(); + _spriteSystem = _entManager.System(); + + // 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(owner, out var xform)) + { + consoleCoords = xform.Coordinates; + NavMap.MapUid = xform.GridUid; + + // Assign station name + if (_entManager.TryGetComponent(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(_owner, out var console)) + return; + + NavMap.TileColor = console.NavMapTileColor; + NavMap.WallColor = console.NavMapWallColor; + + // Initalize + UpdateUI(consoleCoords, Array.Empty()); + } + + #region Toggle handling + + private void OnShowPipeNetworkToggled() + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_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(_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(_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(_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(_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(_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); + } + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml new file mode 100644 index 00000000000..6a19f0775f9 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml @@ -0,0 +1,74 @@ + + + + + diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs new file mode 100644 index 00000000000..0ce0c9c880a --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs @@ -0,0 +1,166 @@ +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(); + _cache = IoCManager.Resolve(); + + Data = data; + + // Modulate colored stripe + NetworkColorStripe.Modulate = data.Color; + + // Load fonts + var headerFont = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Bold.ttf"), 11); + var normalFont = new VectorFont(_cache.GetResource("/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("/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; + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml b/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml new file mode 100644 index 00000000000..9dafac9caea --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml.cs new file mode 100644 index 00000000000..0951ba55264 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml.cs @@ -0,0 +1,25 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +/// +/// ADAPTED FROM SECWATCH - DELTAV +/// + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class PsiWatchEntryControl : BoxContainer +{ + public PsiWatchEntryControl(PsiWatchEntry entry) + { + RobustXamlLoader.Load(this); + + Status.Text = Loc.GetString($"psionics-records-status-{entry.Status.ToString().ToLower()}"); + Title.Text = Loc.GetString("psi-watch-entry", ("name", entry.Name), ("job", entry.Job)); + + Reason.Text = entry.Reason ?? Loc.GetString("psi-watch-no-reason"); + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchUi.cs b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUi.cs new file mode 100644 index 00000000000..40eb2f19e43 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUi.cs @@ -0,0 +1,31 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader; +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.UserInterface; + +/// +/// ADAPTED FROM SECWATCH - DELTAV +/// + +namespace Content.Client.CartridgeLoader.Cartridges; + +public sealed partial class PsiWatchUi : UIFragment +{ + private PsiWatchUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface ui, EntityUid? owner) + { + _fragment = new PsiWatchUiFragment(); + } + + public override void UpdateState(BoundUserInterfaceState state) + { + if (state is PsiWatchUiState cast) + _fragment?.UpdateState(cast); + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml new file mode 100644 index 00000000000..25181e347b3 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml.cs new file mode 100644 index 00000000000..e446581317d --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml.cs @@ -0,0 +1,29 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +/// +/// ADAPTED FROM SECWATCH - DELTAV +/// + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class PsiWatchUiFragment : BoxContainer +{ + public PsiWatchUiFragment() + { + RobustXamlLoader.Load(this); + } + + public void UpdateState(PsiWatchUiState state) + { + NoEntries.Visible = state.Entries.Count == 0; + Entries.RemoveAllChildren(); + foreach (var entry in state.Entries) + { + Entries.AddChild(new PsiWatchEntryControl(entry)); + } + } +} diff --git a/Content.Client/Commands/MappingClientSideSetupCommand.cs b/Content.Client/Commands/MappingClientSideSetupCommand.cs index eb2d13c9540..464f39e6e67 100644 --- a/Content.Client/Commands/MappingClientSideSetupCommand.cs +++ b/Content.Client/Commands/MappingClientSideSetupCommand.cs @@ -24,8 +24,9 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { _entitySystemManager.GetEntitySystem().MarkersVisible = true; _lightManager.Enabled = false; - shell.ExecuteCommand("showsubfloorforever"); - _stateManager.RequestStateChange(); + shell.ExecuteCommand("showsubfloor"); + shell.ExecuteCommand("zoom 1.5"); + shell.ExecuteCommand("scene MappingState"); } } } diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index c1958acba7f..c53ac32fbdd 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -15,6 +15,7 @@ + diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs index dfbbf108917..93cc7e59157 100644 --- a/Content.Client/DoAfter/DoAfterOverlay.cs +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -8,6 +8,8 @@ using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; +using Robust.Shared.Configuration; +using Content.Shared.CCVar; namespace Content.Client.DoAfter; @@ -16,6 +18,7 @@ public sealed class DoAfterOverlay : Overlay private readonly IEntityManager _entManager; private readonly IGameTiming _timing; private readonly IPlayerManager _player; + private readonly IConfigurationManager _cfg; private readonly SharedTransformSystem _transform; private readonly MetaDataSystem _meta; private readonly ProgressColorSystem _progressColor; @@ -32,6 +35,8 @@ public sealed class DoAfterOverlay : Overlay private const float StartX = 2; private const float EndX = 22f; + private bool _useModernHUD = false; + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager, IGameTiming timing, IPlayerManager player) @@ -39,6 +44,7 @@ public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager, _entManager = entManager; _timing = timing; _player = player; + _cfg = IoCManager.Resolve(); _transform = _entManager.EntitySysManager.GetEntitySystem(); _meta = _entManager.EntitySysManager.GetEntitySystem(); _progressColor = _entManager.System(); @@ -46,6 +52,8 @@ public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager, _barTexture = _entManager.EntitySysManager.GetEntitySystem().Frame0(sprite); _unshadedShader = protoManager.Index("unshaded").Instance(); + _useModernHUD = _cfg.GetCVar(CCVars.ModernProgressBar); + _cfg.OnValueChanged(CCVars.ModernProgressBar, (newValue) => { _useModernHUD = newValue; } ); } protected override void Draw(in OverlayDrawArgs args) @@ -139,13 +147,41 @@ protected override void Draw(in OverlayDrawArgs args) { var elapsed = time - doAfter.StartTime; elapsedRatio = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds); - color = GetProgressColor(elapsedRatio, alpha); + + if (_useModernHUD) + { + if (elapsedRatio < 1.0f) + color = GetProgressColor(elapsedRatio, alpha); + else + color = GetProgressColor(0.35f, alpha); // Make orange/yellow color + } + else + { + color = GetProgressColor(elapsedRatio, alpha); + } } var xProgress = (EndX - StartX) * elapsedRatio + StartX; - var box = new Box2(new Vector2(StartX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter); - box = box.Translated(position); - handle.DrawRect(box, color); + + if (_useModernHUD) + { + var box = new Box2(new Vector2(StartX, 2f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 5f) / EyeManager.PixelsPerMeter); + box = box.Translated(position); + // Brighter line, like /tg/station bar + var boxInner = new Box2(new Vector2(StartX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter); + boxInner = boxInner.Translated(position); + + handle.DrawRect(box, color); + handle.DrawRect(boxInner, Color.InterpolateBetween(color, Color.White, 0.5f)); + } + else + { + var box = new Box2(new Vector2(StartX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter); + box = box.Translated(position); + + handle.DrawRect(box, color); + } + offset += _barTexture.Height / scale; } } diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index c7bd719f523..d7c55ea5ed5 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -205,6 +205,9 @@ public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile? profil humanoid.CustomBaseLayers = customBaseLayers; humanoid.Sex = profile.Sex; humanoid.Gender = profile.Gender; + humanoid.DisplayPronouns = profile.DisplayPronouns; + humanoid.StationAiName = profile.StationAiName; + humanoid.CyborgName = profile.CyborgName; humanoid.Age = profile.Age; humanoid.Species = profile.Species; humanoid.SkinColor = profile.Appearance.SkinColor; diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index 7f3ad60c3da..a300e8c0542 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -20,6 +20,7 @@ using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Random; using Robust.Shared.Utility; using static Content.Shared.Humanoid.SharedHumanoidAppearanceSystem; using CharacterSetupGui = Content.Client.Lobby.UI.CharacterSetupGui; @@ -38,6 +39,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered + + + + + + + + +