diff --git a/.github/workflows/reuse-updater.yml b/.github/workflows/reuse-updater.yml index d5031cd53e4..9599952317c 100644 --- a/.github/workflows/reuse-updater.yml +++ b/.github/workflows/reuse-updater.yml @@ -1,7 +1,9 @@ -# SPDX-FileCopyrightText: 2025 Aiden -# SPDX-FileCopyrightText: 2025 Aidenkrz # SPDX-FileCopyrightText: 2025 Redrover1760 -# SPDX-FileCopyrightText: 2025 sleepyyapril +# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com> +# SPDX-FileCopyrightText: 2025 Aidenkrz <28298836+Aidenkrz@users.noreply.github.com> +# SPDX-FileCopyrightText: 2025 Lachryphage (GitHub) +# SPDX-FileCopyrightText: 2025 ReboundQ3 +# SPDX-FileCopyrightText: 2025 sleepyyapril # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -51,7 +53,7 @@ jobs: echo "Error fetching PR files from GitHub API." exit 1 fi - + if ! echo "$PR_FILES_JSON" | jq -e . > /dev/null; then echo "Warning: Received empty or invalid JSON from GitHub API for PR files." PR_FILES="" # Set PR_FILES to empty to avoid errors below @@ -114,4 +116,3 @@ jobs: skip_dirty_check: false skip_fetch: true - \ No newline at end of file diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs index d57f36e74f8..bd213816570 100644 --- a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs @@ -1,5 +1,25 @@ +// SPDX-FileCopyrightText: 2021 20kdc +// SPDX-FileCopyrightText: 2021 Visne <39844191+Visne@users.noreply.github.com> +// SPDX-FileCopyrightText: 2022 Jessica M +// SPDX-FileCopyrightText: 2022 Moony +// SPDX-FileCopyrightText: 2022 ike709 +// SPDX-FileCopyrightText: 2022 ike709 +// SPDX-FileCopyrightText: 2022 mirrorcult +// SPDX-FileCopyrightText: 2022 theashtronaut <112137107+theashtronaut@users.noreply.github.com> +// SPDX-FileCopyrightText: 2022 wrexbe <81056464+wrexbe@users.noreply.github.com> +// SPDX-FileCopyrightText: 2023 Pieter-Jan Briers +// SPDX-FileCopyrightText: 2023 metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> +// SPDX-FileCopyrightText: 2024 Pieter-Jan Briers +// SPDX-FileCopyrightText: 2025 Lachryphage (GitHub) +// SPDX-FileCopyrightText: 2025 ReboundQ3 +// SPDX-FileCopyrightText: 2025 c4llv07e +// SPDX-FileCopyrightText: 2025 hivehum +// +// SPDX-License-Identifier: MIT + using Content.Client.Administration.Managers; using Content.Client.Audio; +using Content.Shared._EE.CCVar; using Content.Shared.CCVar; using Robust.Client.Audio; using Robust.Client.AutoGenerated; diff --git a/Content.Client/_EE/Supermatter/Components/SupermatterVisualizerComponent.cs b/Content.Client/_EE/Supermatter/Components/SupermatterVisualizerComponent.cs new file mode 100644 index 00000000000..d5144ce03c2 --- /dev/null +++ b/Content.Client/_EE/Supermatter/Components/SupermatterVisualizerComponent.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Lachryphage (GitHub) +// SPDX-FileCopyrightText: 2025 ReboundQ3 +// SPDX-FileCopyrightText: 2025 hivehum +// +// SPDX-License-Identifier: MIT + +using Content.Client._EE.Supermatter.Systems; +using Content.Shared._EE.Supermatter.Components; + +namespace Content.Client._EE.Supermatter.Components; + +[RegisterComponent] +[Access(typeof(SupermatterVisualizerSystem))] +public sealed partial class SupermatterVisualsComponent : Component +{ + [DataField("crystal", required: true)] + public Dictionary CrystalVisuals = default!; +} diff --git a/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleBoundUserInterface.cs b/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..e08dddbfb9b --- /dev/null +++ b/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleBoundUserInterface.cs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2025 Lachryphage (GitHub) +// SPDX-FileCopyrightText: 2025 ReboundQ3 +// SPDX-FileCopyrightText: 2025 V <97265903+formlessnameless@users.noreply.github.com> +// SPDX-FileCopyrightText: 2025 hivehum +// SPDX-FileCopyrightText: 2025 mqole <113324899+mqole@users.noreply.github.com> +// +// SPDX-License-Identifier: MIT + +using Content.Shared._EE.Supermatter.Components; + +namespace Content.Client._EE.Supermatter.Consoles; + +public sealed class SupermatterConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) +{ + [ViewVariables] + private SupermatterConsoleWindow? _menu; + + protected override void Open() + { + base.Open(); + + _menu = new SupermatterConsoleWindow(this, Owner); + _menu.OpenCentered(); + _menu.OnClose += Close; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (_menu == null || state is not SupermatterConsoleBoundInterfaceState msg) + return; + + _menu?.UpdateUI(msg.Supermatters, msg.FocusData); + } + + public void SendFocusChangeMessage(NetEntity? netEntity) + { + SendMessage(new SupermatterConsoleFocusChangeMessage(netEntity)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Parent?.RemoveChild(_menu); + } +} diff --git a/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleWindow.xaml b/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleWindow.xaml new file mode 100644 index 00000000000..3d2c638c16f --- /dev/null +++ b/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleWindow.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleWindow.xaml.cs b/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleWindow.xaml.cs new file mode 100644 index 00000000000..9b16fc69360 --- /dev/null +++ b/Content.Client/_EE/Supermatter/Consoles/SupermatterConsoleWindow.xaml.cs @@ -0,0 +1,217 @@ +// SPDX-FileCopyrightText: 2025 Lachryphage (GitHub) +// SPDX-FileCopyrightText: 2025 ReboundQ3 +// SPDX-FileCopyrightText: 2025 V <97265903+formlessnameless@users.noreply.github.com> +// SPDX-FileCopyrightText: 2025 hivehum +// SPDX-FileCopyrightText: 2025 mqole <113324899+mqole@users.noreply.github.com> +// +// SPDX-License-Identifier: MIT + +using Content.Client.Message; +using Content.Client.UserInterface.Controls; +using Content.Shared._EE.Supermatter.Components; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Content.Client._EE.Supermatter.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class SupermatterConsoleWindow : FancyWindow +{ + private readonly IEntityManager _entManager; + + private readonly EntityUid? _owner; + private NetEntity? _trackedEntity; + private SupermatterConsoleEntry[]? _supermatters = null; + + public event Action? SendFocusChangeMessageAction; + + private bool _autoScrollActive = false; + private bool _autoScrollAwaitsUpdate = false; + + public SupermatterConsoleWindow(SupermatterConsoleBoundUserInterface userInterface, EntityUid? owner) + { + RobustXamlLoader.Load(this); + _entManager = IoCManager.Resolve(); + _owner = owner; + + // Set the tracked supermatter for persistence + if (_entManager.TryGetComponent(_owner, out var console)) + _trackedEntity = console.FocusSupermatter; + + // Set supermatter monitoring message action + SendFocusChangeMessageAction += userInterface.SendFocusChangeMessage; + } + + public void UpdateUI(SupermatterConsoleEntry[] supermatters, SupermatterFocusData? focusData) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + if (_trackedEntity != focusData?.NetEntity) + { + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + focusData = null; + } + + // Retain supermatter data for use inbetween updates + _supermatters = supermatters; + + // Clear excess children from the tables + var supermattersCount = _supermatters.Length; + + while (SupermattersTable.ChildCount > supermattersCount) + SupermattersTable.RemoveChild(SupermattersTable.GetChild(SupermattersTable.ChildCount - 1)); + + // Update all entries in each table + for (var index = 0; index < _supermatters.Length; index++) + { + var entry = _supermatters.ElementAt(index); + UpdateUIEntry(entry, index, SupermattersTable, console, focusData); + } + + // If no alerts are active, display a message + if (supermattersCount == 0) + { + var label = new RichTextLabel() + { + HorizontalExpand = true, + VerticalExpand = true, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + }; + + label.SetMarkup(Loc.GetString("supermatter-console-window-no-supermatters")); + + SupermattersTable.AddChild(label); + } + + // Auto-scroll re-enable + if (_autoScrollAwaitsUpdate) + { + _autoScrollActive = true; + _autoScrollAwaitsUpdate = false; + } + } + + private void UpdateUIEntry(SupermatterConsoleEntry entry, int index, Control table, SupermatterConsoleComponent console, SupermatterFocusData? focusData = null) + { + // Make new UI entry if required + if (index >= table.ChildCount) + { + var newEntryContainer = new SupermatterEntryContainer(entry.NetEntity); + + // On click + newEntryContainer.FocusButton.OnButtonUp += args => + { + if (_trackedEntity == newEntryContainer.NetEntity) + _trackedEntity = null; + else + _trackedEntity = newEntryContainer.NetEntity; + + // Send message to console that the focus has changed + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + }; + + // Add the entry to the current table + table.AddChild(newEntryContainer); + } + + // Update values and UI elements + var tableChild = table.GetChild(index); + + if (tableChild is not SupermatterEntryContainer) + { + table.RemoveChild(tableChild); + UpdateUIEntry(entry, index, table, console, focusData); + + return; + } + + var entryContainer = (SupermatterEntryContainer)tableChild; + + entryContainer.UpdateEntry(entry, entry.NetEntity == _trackedEntity, focusData); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + AutoScrollToFocus(); + } + + private void AutoScrollToFocus() + { + if (!_autoScrollActive) + return; + + if (SupermattersTable.Parent is not ScrollContainer scroll) + 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 static bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar) + { + vScrollBar = null; + + foreach (var child in scroll.Children) + { + if (child is not VScrollBar castChild) + continue; + + vScrollBar = castChild; + return true; + } + + return false; + } + + private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition) + { + nextScrollPosition = null; + + if (SupermattersTable.Parent is not ScrollContainer scroll) + return false; + + if (scroll.Children.ElementAt(0) is not BoxContainer container || + !container.Children.Any()) + return false; + + // Exit if the heights of the children haven't been initialized yet + if (!container.Children.Any(x => x.Height > 0)) + return false; + + nextScrollPosition = 0; + + foreach (var control in container.Children) + { + if (control == null || control is not SupermatterEntryContainer) + continue; + + if (((SupermatterEntryContainer)control).NetEntity == _trackedEntity) + return true; + + nextScrollPosition += control.Height; + } + + // Failed to find control + nextScrollPosition = null; + + return false; + } +} diff --git a/Content.Client/_EE/Supermatter/Consoles/SupermatterEntryContainer.xaml b/Content.Client/_EE/Supermatter/Consoles/SupermatterEntryContainer.xaml new file mode 100644 index 00000000000..337aecd4047 --- /dev/null +++ b/Content.Client/_EE/Supermatter/Consoles/SupermatterEntryContainer.xaml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_EE/Supermatter/Consoles/SupermatterEntryContainer.xaml.cs b/Content.Client/_EE/Supermatter/Consoles/SupermatterEntryContainer.xaml.cs new file mode 100644 index 00000000000..0a342fd5e35 --- /dev/null +++ b/Content.Client/_EE/Supermatter/Consoles/SupermatterEntryContainer.xaml.cs @@ -0,0 +1,395 @@ +// SPDX-FileCopyrightText: 2025 Lachryphage (GitHub) +// SPDX-FileCopyrightText: 2025 ReboundQ3 +// SPDX-FileCopyrightText: 2025 V <97265903+formlessnameless@users.noreply.github.com> +// SPDX-FileCopyrightText: 2025 hivehum +// SPDX-FileCopyrightText: 2025 mqole <113324899+mqole@users.noreply.github.com> +// +// SPDX-License-Identifier: MIT + +using Content.Client.Stylesheets; +using Content.Shared._EE.CCVar; +using Content.Shared._EE.Supermatter.Components; +using Content.Shared.Atmos; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Configuration; +using System.Linq; +using System.Numerics; + +namespace Content.Client._EE.Supermatter.Consoles; +using Robust.Shared.Maths; + +[GenerateTypedNameReferences] +public sealed partial class SupermatterEntryContainer : BoxContainer +{ + public NetEntity NetEntity; + + private readonly IConfigurationManager _config; + private readonly IEntityManager _entManager; + private readonly IResourceCache _cache; + + private readonly Dictionary? _engineDictionary; + private readonly List<(SupermatterGasBarContainer Bar, Gas Gas)> _gasBarData; + private readonly List<(BoxContainer Container, Button Button, TextureRect Arrow)> _expandDetails; + + // Colors + private readonly Color _colorGray = Color.Gray; + private readonly Color _colorSlate = Color.LightSlateGray; + private readonly Color _colorRed = StyleNano.DangerousRedFore; + private readonly Color _colorOrange = StyleNano.ConcerningOrangeFore; + private readonly Color _colorGreen = StyleNano.GoodGreenFore; + private readonly Color _colorTurquoise = Color.FromHex("#00fff7"); + + // Arrow icons + private readonly string _arrowUp = "/Textures/_EE/Interface/Supermatter/arrow_up.png"; + private readonly string _arrowDown = "/Textures/_EE/Interface/Supermatter/arrow_down.png"; + + // Supermatter base values + private readonly float _radiationBase; + private readonly float _temperatureLimitBase; + private readonly float _wasteBase; + + // Save focus data values so we don't update too often + private SupermatterFocusData _focusData; + + // Other saved variables + private bool _showAllGases; + + public SupermatterEntryContainer(NetEntity uid) + { + RobustXamlLoader.Load(this); + + _config = IoCManager.Resolve(); + _entManager = IoCManager.Resolve(); + _cache = IoCManager.Resolve(); + + NetEntity = uid; + + #region List/Dictionary Definitions + + // Set the engine dictionary + _engineDictionary = new() + { + { "integrity", ( IntegrityBarLabel, IntegrityBar, IntegrityBarBorder, 0.9f, 0.1f, _colorRed, _colorOrange, _colorGreen ) }, + { "power", ( PowerBarLabel, PowerBar, PowerBarBorder, 0.9f, 0.1f, _colorGreen, _colorOrange, _colorRed ) }, + { "radiation", ( RadiationBarLabel, RadiationBar, RadiationBarBorder, 0.1f, 0.9f, _colorGreen, _colorOrange, _colorRed ) }, + { "moles", ( MolesBarLabel, MolesBar, MolesBarBorder, 0.5f, 0.5f, _colorGreen, _colorOrange, _colorRed ) }, + { "temperature", ( TemperatureBarLabel, TemperatureBar, TemperatureBarBorder, 0.9f, 0.1f, _colorTurquoise, _colorGreen, _colorRed ) }, + { "waste", ( WasteBarLabel, WasteBar, WasteBarBorder, 0.5f, 0.5f, _colorGreen, _colorOrange, _colorRed ) } + }; + + // Set the gas bar data + _gasBarData = []; + var gasData = Enum.GetValues(); + + foreach (var gas in gasData) + { + var data = (new SupermatterGasBarContainer(gas), gas); + _gasBarData.Add(data); + } + + // Set the container, button, and arrow texture for details expansion + _expandDetails = new() + { + ( IntegrityDetailsContainer, IntegrityButton, IntegrityButtonArrow ), + ( PowerDetailsContainer, PowerButton, PowerButtonArrow ), + ( RadiationDetailsContainer, RadiationButton, RadiationButtonArrow ), + ( TemperatureLimitDetailsContainer, TemperatureLimitButton, TemperatureLimitButtonArrow ), + ( WasteDetailsContainer, WasteButton, WasteButtonArrow ) + }; + + var mainLabels = new List