diff --git a/.github/ISSUE_TEMPLATE/heisentest_template.md b/.github/ISSUE_TEMPLATE/heisentest_template.md index 2fde892b79..3e6d2ee5fc 100644 --- a/.github/ISSUE_TEMPLATE/heisentest_template.md +++ b/.github/ISSUE_TEMPLATE/heisentest_template.md @@ -6,6 +6,9 @@ labels: autogenerated A test failure has been detected on the main branch. This should never occur. +Commit under test: {{ env.GITHUB_SHA }} +[Link to failing run]({{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}) + > [!NOTE] > This issue is automatically generated by a github action and will be updated when this failure reoccurs. > Do not edit the body of this issue. diff --git a/.github/workflows/heisendetector.yml b/.github/workflows/heisendetector.yml index c72c539684..2e13c8499d 100644 --- a/.github/workflows/heisendetector.yml +++ b/.github/workflows/heisendetector.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout Master uses: actions/checkout@v6 with: - submodules: recursive + submodules: recursive - name: Setup .NET Core uses: actions/setup-dotnet@v4.1.0 @@ -81,6 +81,10 @@ jobs: FULLNAME: ${{ matrix.value.fullname }} OUTPUT: ${{ matrix.value.output }} FAILURE: ${{ matrix.value.failure }} + GITHUB_SERVER_URL: ${{ vars.GITHUB_SERVER_URL }} + GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }} + GITHUB_RUN_ID: ${{ vars.GITHUB_RUN_ID }} + GITHUB_SHA: ${{ vars.GITHUB_SHA }} with: filename: .github/ISSUE_TEMPLATE/heisentest_template.md update_existing: true diff --git a/Content.Client/Access/AccessOverlay.cs b/Content.Client/Access/AccessOverlay.cs index f0f2890d24..234164529c 100644 --- a/Content.Client/Access/AccessOverlay.cs +++ b/Content.Client/Access/AccessOverlay.cs @@ -1,6 +1,7 @@ using System.Text; using Content.Client.Resources; using Content.Shared.Access.Components; +using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.Enums; @@ -13,12 +14,12 @@ public sealed class AccessOverlay : Overlay private const int TextFontSize = 12; private readonly IEntityManager _entityManager; - private readonly SharedTransformSystem _transformSystem; + private readonly TransformSystem _transformSystem; private readonly Font _font; public override OverlaySpace Space => OverlaySpace.ScreenSpace; - public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem) + public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, TransformSystem transformSystem) { _entityManager = entityManager; _transformSystem = transformSystem; @@ -87,7 +88,7 @@ public sealed class AccessOverlay : Overlay } var accessInfoText = textBuffer.ToString(); - var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetWorldPosition(transform)); + var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetRenderWorldPosition((uid, transform))); args.ScreenHandle.DrawString(_font, screenPos, accessInfoText, 1, Color.Gold, TextOutline.Default); } } diff --git a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs index f471975ba3..0ae6f1a7ea 100644 --- a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs +++ b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs @@ -1,3 +1,4 @@ +using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.Console; @@ -8,7 +9,7 @@ public sealed partial class ShowAccessReadersCommand : LocalizedEntityCommands { [Dependency] private IOverlayManager _overlay = default!; [Dependency] private IResourceCache _cache = default!; - [Dependency] private SharedTransformSystem _xform = default!; + [Dependency] private TransformSystem _xform = default!; public override string Command => "showaccessreaders"; diff --git a/Content.Client/Actions/ActionIconVisualsSystem.cs b/Content.Client/Actions/ActionIconVisualsSystem.cs index fdaceecc4e..da1a2c6105 100644 --- a/Content.Client/Actions/ActionIconVisualsSystem.cs +++ b/Content.Client/Actions/ActionIconVisualsSystem.cs @@ -7,12 +7,13 @@ namespace Content.Client.Actions; public sealed partial class ActionIconVisualsSystem : VisualizerSystem { + /// protected override void OnAppearanceChange(EntityUid uid, ActionComponent comp, ref AppearanceChangeEvent args) { if (args.Sprite == null) return; - if (AppearanceSystem.TryGetData(uid, ActionState.DynamicIcon, out var icon, args.Component)) + if (args.TryGetData(ActionState.DynamicIcon, out var icon)) { if (icon is SpriteSpecifier.EntityPrototype) SpriteSystem.LayerSetTexture((uid, args.Sprite), ActionVisuals.Icon, SpriteSystem.Frame0(icon)); @@ -20,7 +21,7 @@ public sealed partial class ActionIconVisualsSystem : VisualizerSystem(uid, ActionState.Color, out var color, args.Component)) + if (args.TryGetData(ActionState.Color, out var color)) { SpriteSystem.LayerSetColor((uid, args.Sprite), ActionVisuals.Icon, color); diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs index b88bd1728f..d3becbf6bd 100644 --- a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs +++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs @@ -42,7 +42,7 @@ public sealed partial class AdminMessagePopupWindow : Control public void SetState(AdminMessageEuiState state) { - Timer = (float) state.Time.TotalSeconds; + Timer = (float)state.Time.TotalSeconds; MessageContainer.RemoveAllChildren(); @@ -52,7 +52,7 @@ public sealed partial class AdminMessagePopupWindow : Control } Description.SetMessage( - FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length)))); + FormattedMessage.FromMarkupOrThrow(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length)))); } private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj) diff --git a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs index c4dd442311..65ed73d261 100644 --- a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs +++ b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs @@ -154,7 +154,6 @@ public sealed partial class ExplosionDebugOverlay : Overlay handle.SetTransform(SpaceMatrix); DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize); - handle.SetTransform(Matrix3x2.Identity); } private void DrawTiles( diff --git a/Content.Client/AlertLevel/AlertLevelDisplaySystem.cs b/Content.Client/AlertLevel/AlertLevelDisplaySystem.cs index b366ef6207..50f75157d5 100644 --- a/Content.Client/AlertLevel/AlertLevelDisplaySystem.cs +++ b/Content.Client/AlertLevel/AlertLevelDisplaySystem.cs @@ -8,23 +8,19 @@ namespace Content.Client.AlertLevel; public sealed partial class AlertLevelDisplaySystem : EntitySystem { [Dependency] private SpriteSystem _sprite = default!; - [Dependency] private SharedAppearanceSystem _appearance = default!; [SubscribeLocalEvent] private void OnAppearanceChange(EntityUid uid, AlertLevelDisplayComponent alertLevelDisplay, ref AppearanceChangeEvent args) { if (args.Sprite == null) - { return; - } + var layer = _sprite.LayerMapReserve((uid, args.Sprite), AlertLevelDisplay.Layer); - if (_appearance.TryGetData(uid, AlertLevelDisplay.Powered, out var powered, component: args.Component)) - { + if (args.TryGetData(AlertLevelDisplay.Powered, out var powered)) _sprite.LayerSetVisible((uid, args.Sprite), layer, powered); - } - if (!_appearance.TryGetData>(uid, AlertLevelDisplay.CurrentLevel, out var level, component: args.Component)) + if (!args.TryGetData>(AlertLevelDisplay.CurrentLevel, out var level)) { _sprite.LayerSetRsiState((uid, args.Sprite), layer, alertLevelDisplay.AlertVisuals.Values.First()); return; diff --git a/Content.Client/Anomaly/AnomalyScannerSystem.cs b/Content.Client/Anomaly/AnomalyScannerSystem.cs index f30a213977..1145217d76 100644 --- a/Content.Client/Anomaly/AnomalyScannerSystem.cs +++ b/Content.Client/Anomaly/AnomalyScannerSystem.cs @@ -34,7 +34,7 @@ public sealed partial class AnomalyScannerSystem : SharedAnomalyScannerSystem private void OnComponentInit(Entity ent, ref ComponentInit args) { - if(!_sprite.TryGetLayer(ent.Owner, AnomalyScannerVisualLayers.Base, out var layer, true)) + if (!_sprite.TryGetLayer(ent.Owner, AnomalyScannerVisualLayers.Base, out var layer, true)) return; // Allocate the OwnedTexture @@ -70,9 +70,8 @@ public sealed partial class AnomalyScannerSystem : SharedAnomalyScannerSystem if (args.Sprite is null || ent.Comp.ScreenTexture is null || ent.Comp.BarBuf is null) return; - args.AppearanceData.TryGetValue(AnomalyScannerVisuals.AnomalySeverity, out var severityObj); - if (severityObj is not float severity) - severity = 0; + if (!args.TryGetData(AnomalyScannerVisuals.AnomalySeverity, out var severity)) + severity = 0.0f; // Get the bar length var barLength = (int)(severity * ent.Comp.Size.X); @@ -80,16 +79,16 @@ public sealed partial class AnomalyScannerSystem : SharedAnomalyScannerSystem // Calculate the bar color // Hue "angle" of two colors to interpolate between depending on severity // Just a lerp from Green hue at severity = 0.5 to Red hue at 1.0 - var hue = Math.Clamp(2*GreenHue * (1 - severity), RedHue, GreenHue); + var hue = Math.Clamp(2 * GreenHue * (1 - severity), RedHue, GreenHue); var color = new Rgba32(Color.FromHsv(new Vector4(hue, 1f, 1f, 1f)).RGBA); var transparent = new Rgba32(0, 0, 0, 255); - for(var y = 0; y < ent.Comp.Size.Y; y++) + for (var y = 0; y < ent.Comp.Size.Y; y++) { for (var x = 0; x < ent.Comp.Size.X; x++) { - ent.Comp.BarBuf[y*ent.Comp.Size.X + x] = x < barLength ? color : transparent; + ent.Comp.BarBuf[y * ent.Comp.Size.X + x] = x < barLength ? color : transparent; } } diff --git a/Content.Client/Anomaly/AnomalySystem.cs b/Content.Client/Anomaly/AnomalySystem.cs index 839880acef..d67e3ada42 100644 --- a/Content.Client/Anomaly/AnomalySystem.cs +++ b/Content.Client/Anomaly/AnomalySystem.cs @@ -46,10 +46,10 @@ public sealed partial class AnomalySystem : SharedAnomalySystem if (args.Sprite is not { } sprite) return; - if (!Appearance.TryGetData(uid, AnomalyVisuals.IsPulsing, out var pulsing, args.Component)) + if (!args.TryGetData(AnomalyVisuals.IsPulsing, out var pulsing)) pulsing = false; - if (Appearance.TryGetData(uid, AnomalyVisuals.Supercritical, out var super, args.Component) && super) + if (args.TryGetData(AnomalyVisuals.Supercritical, out var super) && super) pulsing = super; if (HasComp(uid)) diff --git a/Content.Client/Atmos/EntitySystems/AtmosPipeAppearanceSystem.cs b/Content.Client/Atmos/EntitySystems/AtmosPipeAppearanceSystem.cs index 4ee3d74545..3337623469 100644 --- a/Content.Client/Atmos/EntitySystems/AtmosPipeAppearanceSystem.cs +++ b/Content.Client/Atmos/EntitySystems/AtmosPipeAppearanceSystem.cs @@ -75,13 +75,13 @@ public sealed partial class AtmosPipeAppearanceSystem : SharedAtmosPipeAppearanc var numberOfPipeLayers = GetNumberOfPipeLayers(uid, out var atmosPipeLayers); - if (!_appearance.TryGetData(uid, PipeVisuals.VisualState, out var worldConnectedDirections, args.Component)) + if (!args.TryGetData(PipeVisuals.VisualState, out var worldConnectedDirections)) { HideAllPipeConnection((uid, args.Sprite), atmosPipeLayers, numberOfPipeLayers); return; } - if (!_appearance.TryGetData(uid, PipeColorVisuals.Color, out var color, args.Component)) + if (!args.TryGetData(PipeColorVisuals.Color, out var color)) color = Color.White; for (byte i = 0; i < numberOfPipeLayers; i++) diff --git a/Content.Client/Atmos/EntitySystems/AtmosPipeLayersSystem.cs b/Content.Client/Atmos/EntitySystems/AtmosPipeLayersSystem.cs index bdd653ad8f..456f214416 100644 --- a/Content.Client/Atmos/EntitySystems/AtmosPipeLayersSystem.cs +++ b/Content.Client/Atmos/EntitySystems/AtmosPipeLayersSystem.cs @@ -14,30 +14,23 @@ namespace Content.Client.Atmos.EntitySystems; /// public sealed partial class AtmosPipeLayersSystem : SharedAtmosPipeLayersSystem { - [Dependency] private SharedAppearanceSystem _appearance = default!; [Dependency] private IReflectionManager _reflection = default!; [Dependency] private IResourceCache _resourceCache = default!; [Dependency] private SpriteSystem _sprite = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnAppearanceChange); - } - - private void OnAppearanceChange(Entity ent, ref AppearanceChangeEvent ev) + [SubscribeLocalEvent] + private void OnAppearanceChange(Entity ent, ref AppearanceChangeEvent args) { if (!TryComp(ent, out var sprite)) return; - if (_appearance.TryGetData(ent, AtmosPipeLayerVisuals.Sprite, out var spriteRsi) && + if (args.TryGetData(AtmosPipeLayerVisuals.Sprite, out var spriteRsi) && _resourceCache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / spriteRsi, out RSIResource? resource)) { _sprite.SetBaseRsi((ent, sprite), resource.RSI); } - if (_appearance.TryGetData>(ent, AtmosPipeLayerVisuals.SpriteLayers, out var pipeState)) + if (args.TryGetData>(AtmosPipeLayerVisuals.SpriteLayers, out var pipeState)) { foreach (var (layerKey, rsiPath) in pipeState) { diff --git a/Content.Client/Atmos/EntitySystems/GasCanisterAppearanceSystem.cs b/Content.Client/Atmos/EntitySystems/GasCanisterAppearanceSystem.cs index 1d0bf5d315..ec49c2609b 100644 --- a/Content.Client/Atmos/EntitySystems/GasCanisterAppearanceSystem.cs +++ b/Content.Client/Atmos/EntitySystems/GasCanisterAppearanceSystem.cs @@ -5,13 +5,13 @@ using Robust.Client.GameObjects; namespace Content.Client.Atmos.EntitySystems; /// -/// Used to change the appearance of gas canisters. +/// Used to change the appearance of gas canisters when painted. /// public sealed partial class GasCanisterAppearanceSystem : VisualizerSystem { protected override void OnAppearanceChange(EntityUid uid, GasCanisterComponent component, ref AppearanceChangeEvent args) { - if (!AppearanceSystem.TryGetData(uid, PaintableVisuals.Prototype, out var protoName, args.Component) || args.Sprite is not { } old) + if (!args.TryGetData(PaintableVisuals.Prototype, out var protoName) || args.Sprite is null) return; if (!ProtoMan.HasIndex(protoName)) diff --git a/Content.Client/Atmos/EntitySystems/GasTileVacuumOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileVacuumOverlaySystem.cs new file mode 100644 index 0000000000..19dcd5bfec --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/GasTileVacuumOverlaySystem.cs @@ -0,0 +1,65 @@ +using Content.Client.Atmos.Overlays; +using Content.Shared.CCVar; +using JetBrains.Annotations; +using Robust.Client.Graphics; +using Robust.Shared.Configuration; + +namespace Content.Client.Atmos.EntitySystems; + +/// +/// System responsible for rendering vacuum effects using . +/// +[UsedImplicitly] +public sealed partial class GasTileVacuumOverlaySystem : EntitySystem +{ + [Dependency] private IOverlayManager _overlayMan = default!; + [Dependency] private IConfigurationManager _cfgManager = default!; + + private GasTileVacuumOverlay? _gasTileVacuumOverlay; + private bool _vacuumOverlayEnabledDueToCvar; // due to CVar being enabled + private bool _vacuumOverlayEnabledIntensity; // due to non zero intensity in setting + + public override void Initialize() + { + base.Initialize(); + + _cfgManager.OnValueChanged(CCVars.VacuumOverlay, OnVacuumOverlayChanged, true); + _cfgManager.OnValueChanged(CCVars.VacuumOverlayIntensity, OnVacuumOverlayIntensityChanged, true); + } + + public override void Shutdown() + { + base.Shutdown(); + + _cfgManager.UnsubValueChanged(CCVars.VacuumOverlay, OnVacuumOverlayChanged); + _cfgManager.UnsubValueChanged(CCVars.VacuumOverlayIntensity, OnVacuumOverlayIntensityChanged); + _overlayMan.RemoveOverlay(); + } + + private void OnVacuumOverlayChanged(bool enabled) + { + _vacuumOverlayEnabledDueToCvar = enabled; + UpdateOverlay(); + } + + private void OnVacuumOverlayIntensityChanged(float intensity) + { + _vacuumOverlayEnabledIntensity = intensity > 0f; + UpdateOverlay(); + } + + private void UpdateOverlay() + { + if (_vacuumOverlayEnabledDueToCvar && _vacuumOverlayEnabledIntensity) + { + _gasTileVacuumOverlay ??= new GasTileVacuumOverlay(); + + if (!_overlayMan.HasOverlay()) + _overlayMan.AddOverlay(_gasTileVacuumOverlay); + } + else + { + _overlayMan.RemoveOverlay(); + } + } +} diff --git a/Content.Client/Atmos/EntitySystems/MaxPressureVisualsSystem.cs b/Content.Client/Atmos/EntitySystems/MaxPressureVisualsSystem.cs index e27ac45dac..1e72903803 100644 --- a/Content.Client/Atmos/EntitySystems/MaxPressureVisualsSystem.cs +++ b/Content.Client/Atmos/EntitySystems/MaxPressureVisualsSystem.cs @@ -14,13 +14,7 @@ public sealed partial class MaxPressureVisualsSystem : EntitySystem { [Dependency] private SpriteSystem _sprite = default!; - /// - public override void Initialize() - { - SubscribeLocalEvent(OnMaxPressureInit); - SubscribeLocalEvent(OnAppearanceChange); - } - + [SubscribeLocalEvent] private void OnMaxPressureInit(Entity entity, ref ComponentInit args) { if (!TryComp(entity, out var sprite)) @@ -28,28 +22,27 @@ public sealed partial class MaxPressureVisualsSystem : EntitySystem ArgumentOutOfRangeException.ThrowIfNegativeOrZero(entity.Comp.IntegritySteps); - if (_sprite.LayerMapTryGet((entity, sprite), MaxPressureVisualLayers.Base, out _, false)) + if (_sprite.LayerMapTryGet((entity, sprite), MaxPressureVisualLayers.Base, out var layerIndex, false)) { - _sprite.LayerSetRsiState((entity, sprite), MaxPressureVisualLayers.Base, $"{entity.Comp.IntegrityMask}"); - _sprite.LayerSetVisible((entity, sprite), MaxPressureVisualLayers.Base, false); + _sprite.LayerSetRsiState((entity, sprite), layerIndex, $"{entity.Comp.IntegrityMask}"); + _sprite.LayerSetVisible((entity, sprite), layerIndex, false); } - if (_sprite.LayerMapTryGet((entity, sprite), MaxPressureVisualLayers.BaseUnshaded, out _, false)) + if (_sprite.LayerMapTryGet((entity, sprite), MaxPressureVisualLayers.BaseUnshaded, out layerIndex, false)) { - _sprite.LayerSetRsiState((entity, sprite), MaxPressureVisualLayers.BaseUnshaded, $"{entity.Comp.IntegrityState}-unshaded-0"); - _sprite.LayerSetVisible((entity, sprite), MaxPressureVisualLayers.BaseUnshaded, false); + _sprite.LayerSetRsiState((entity, sprite), layerIndex, $"{entity.Comp.IntegrityState}-unshaded-0"); + _sprite.LayerSetVisible((entity, sprite), layerIndex, false); } } + [SubscribeLocalEvent] private void OnAppearanceChange(Entity entity, ref AppearanceChangeEvent args) { if (args.Sprite is not { } sprite) return; - if (!args.AppearanceData.TryGetValue(GasIntegrity.Integrity, out var obj) || obj is not float integrity) - return; - - if (!args.AppearanceData.TryGetValue(GasIntegrity.MaxIntegrity, out obj) || obj is not float maxIntegrity) + if (!args.TryGetData(GasIntegrity.Integrity, out var integrity) + || !args.TryGetData(GasIntegrity.MaxIntegrity, out var maxIntegrity)) return; // We don't want visuals at max integrity, so we return if we're at max. diff --git a/Content.Client/Atmos/EntitySystems/PipeColorVisualizerSystem.cs b/Content.Client/Atmos/EntitySystems/PipeColorVisualizerSystem.cs index 2a5029ee19..e21a6da20e 100644 --- a/Content.Client/Atmos/EntitySystems/PipeColorVisualizerSystem.cs +++ b/Content.Client/Atmos/EntitySystems/PipeColorVisualizerSystem.cs @@ -8,13 +8,15 @@ public sealed partial class PipeColorVisualizerSystem : VisualizerSystem(uid, out var sprite) - && AppearanceSystem.TryGetData(uid, PipeColorVisuals.Color, out var color, args.Component)) - { - // T-ray scanner / sub floor runs after this visualizer. Lets not bulldoze transparency. - var layer = sprite[PipeVisualLayers.Pipe]; - layer.Color = color.WithAlpha(layer.Color.A); - } + if (args.Sprite == null) + return; + + if (!args.TryGetData(PipeColorVisuals.Color, out var color)) + return; + + // T-ray scanner / sub floor runs after this visualizer. Lets not bulldoze transparency. + var layer = args.Sprite[PipeVisualLayers.Pipe]; + layer.Color = color.WithAlpha(layer.Color.A); } } diff --git a/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs b/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs index 3291fbbece..0d88109b89 100644 --- a/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs +++ b/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs @@ -12,11 +12,8 @@ public sealed partial class AtmosAlarmableVisualsSystem : VisualizerSystem(PowerDeviceVisuals.Powered, out var powered)) return; - } if (component.HideOnDepowered != null) { @@ -36,8 +33,7 @@ public sealed partial class AtmosAlarmableVisualsSystem : VisualizerSystem(AtmosMonitorVisuals.AlarmType, out var alarmType) && powered && component.AlarmStates.TryGetValue(alarmType, out var state)) { diff --git a/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs b/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs index d0aaeade89..a5c7671f5d 100644 --- a/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs +++ b/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs @@ -65,8 +65,6 @@ public sealed partial class AtmosDebugOverlay : Overlay handle.SetTransform(_transform.GetWorldMatrix(grid)); DrawData(msg, handle); } - - handle.SetTransform(Matrix3x2.Identity); } private void DrawData(DebugMessage msg, diff --git a/Content.Client/Atmos/Overlays/GasTileDangerousTemperatureOverlay.cs b/Content.Client/Atmos/Overlays/GasTileDangerousTemperatureOverlay.cs index bdd62db9a8..fa0bbf58f4 100644 --- a/Content.Client/Atmos/Overlays/GasTileDangerousTemperatureOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileDangerousTemperatureOverlay.cs @@ -222,8 +222,6 @@ public sealed partial class GasTileDangerousTemperatureOverlay : Overlay }, new Color(0, 0, 0, 0)); - drawHandle.SetTransform(Matrix3x2.Identity); - return true; } @@ -233,7 +231,6 @@ public sealed partial class GasTileDangerousTemperatureOverlay : Overlay if (res.TemperatureTarget != null) args.WorldHandle.DrawTextureRect(res.TemperatureTarget.Texture, args.WorldBounds); - args.WorldHandle.SetTransform(Matrix3x2.Identity); } protected override void DisposeBehavior() diff --git a/Content.Client/Atmos/Overlays/GasTileFireOverlay.cs b/Content.Client/Atmos/Overlays/GasTileFireOverlay.cs index 370ed2a792..82408284e1 100644 --- a/Content.Client/Atmos/Overlays/GasTileFireOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileFireOverlay.cs @@ -164,8 +164,5 @@ public sealed partial class GasTileFireOverlay : Overlay return true; }); - - drawHandle.UseShader(null); - drawHandle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/Atmos/Overlays/GasTileHeatBlurOverlay.cs b/Content.Client/Atmos/Overlays/GasTileHeatBlurOverlay.cs index 43b95b975a..54e9351890 100644 --- a/Content.Client/Atmos/Overlays/GasTileHeatBlurOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileHeatBlurOverlay.cs @@ -198,8 +198,6 @@ public sealed partial class GasTileHeatBlurOverlay : Overlay // no distortion, no need to render if (!anyDistortion) { - args.WorldHandle.UseShader(null); - args.WorldHandle.SetTransform(Matrix3x2.Identity); return false; } @@ -218,9 +216,6 @@ public sealed partial class GasTileHeatBlurOverlay : Overlay args.WorldHandle.UseShader(_shader); args.WorldHandle.DrawTextureRect(res.HeatTarget.Texture, args.WorldBounds); - - args.WorldHandle.UseShader(null); - args.WorldHandle.SetTransform(Matrix3x2.Identity); } protected override void DisposeBehavior() diff --git a/Content.Client/Atmos/Overlays/GasTileVacuumOverlay.cs b/Content.Client/Atmos/Overlays/GasTileVacuumOverlay.cs new file mode 100644 index 0000000000..0ddcd64a27 --- /dev/null +++ b/Content.Client/Atmos/Overlays/GasTileVacuumOverlay.cs @@ -0,0 +1,196 @@ +using System.Numerics; +using Content.Client.Graphics; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; +using Content.Shared.CCVar; +using Robust.Client.Graphics; +using Robust.Shared.Configuration; +using Robust.Shared.Enums; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Color = Robust.Shared.Maths.Color; + +namespace Content.Client.Atmos.Overlays; + +/// +/// Overlay responsible for rendering vacuum overlay. +/// +public sealed partial class GasTileVacuumOverlay : Overlay +{ + public override bool RequestScreenTexture { get; set; } = true; + private static readonly ProtoId UnshadedShader = "unshaded"; + private static readonly ProtoId VacuumOverlayShader = "VacuumDesaturation"; + private static readonly Color EmptyColor = new(0, 0, 0, 0); + private static readonly Color MarkerColor = new(255f, 0, 0); + + [Dependency] private IEntityManager _entManager = default!; + [Dependency] private IPrototypeManager _proto = default!; + [Dependency] private IClyde _clyde = default!; + [Dependency] private IConfigurationManager _configManager = default!; + + private readonly SharedMapSystem _maps; + private readonly SharedTransformSystem _xformSys; + private readonly ShaderInstance _unshader; + private readonly ShaderInstance _shader; + + private List> _intersectingGrids = new(); + private readonly OverlayResourceCache _resources = new(); + + // Overlay settings + private float _intensity; // overlay intensity. 0.0f = turned off, 1.0f = full grayscale + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + public GasTileVacuumOverlay() + { + IoCManager.InjectDependencies(this); + _maps = _entManager.System(); + _xformSys = _entManager.System(); + + _unshader = _proto.Index(UnshadedShader).Instance(); + _shader = _proto.Index(VacuumOverlayShader).InstanceUnique(); + _configManager.OnValueChanged(CCVars.VacuumOverlayIntensity, SetVacuumOverlayIntensity, invokeImmediately: true); + } + + private void SetVacuumOverlayIntensity(float intensity) + { + _intensity = MathHelper.Clamp(intensity, 0f, 1f); + _shader.SetParameter("intensity", _intensity); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (args.MapId == MapId.Nullspace) + return false; + + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + + var target = args.Viewport.RenderTarget; + + // Probably the resolution of the game window changed, remake the textures. + if (res.VacuumTarget?.Texture.Size != target.Size) + { + res.VacuumTarget?.Dispose(); + res.VacuumTarget = _clyde.CreateRenderTarget( + target.Size, + new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), + name: $"{nameof(GasTileVacuumOverlay)}-blur"); + } + + var overlayQuery = _entManager.GetEntityQuery(); + + args.WorldHandle.UseShader(_unshader); + + var mapId = args.MapId; + var worldAABB = args.WorldAABB; + var worldBounds = args.WorldBounds; + var worldHandle = args.WorldHandle; + var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix(); + + // If there is no Vacuum after checking all visible tiles, we can bail early + var anyVacuum = false; + + // We're rendering in the context of the vacuum target texture, which will encode data as to where vacuum effect will be + args.WorldHandle.RenderInRenderTarget(res.VacuumTarget, + () => + { + _intersectingGrids.Clear(); + _maps.FindGridsIntersecting(mapId, worldAABB, ref _intersectingGrids); + foreach (var grid in _intersectingGrids) + { + if (!overlayQuery.TryGetComponent(grid.Owner, out var comp)) + continue; + + var (_, _, gridEntToWorld, worldToGridLocal) = _xformSys.GetWorldPositionRotationMatrixWithInv(grid.Owner); + var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal; + + if (!Matrix3x2.Invert(gridEntToViewportLocal, out _)) + continue; + + // Draw commands (like DrawRect) will be using grid coordinates from here + worldHandle.SetTransform(gridEntToViewportLocal); + + // We only care about tiles that fit in these bounds + var floatBounds = worldToGridLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize); + + var localBounds = new Box2i( + (int)MathF.Floor(floatBounds.Left), + (int)MathF.Floor(floatBounds.Bottom), + (int)MathF.Ceiling(floatBounds.Right), + (int)MathF.Ceiling(floatBounds.Top)); + + // for each tile and its gas ---> + foreach (var chunk in comp.Chunks.Values) + { + var enumerator = new GasChunkEnumerator(chunk); + + while (enumerator.MoveNext(out var tileGas)) + { + // Check and make sure the tile is within the viewport/screen + var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y); + if (!localBounds.Contains(tilePosition)) + continue; + + if (tileGas.ByteGasTemperature.Value != ThermalByte.StateVacuum) + continue; + + // Encode the strength in the red channel + // alpha set to 1 as tile is active + worldHandle.DrawRect(Box2.CenteredAround(tilePosition + grid.Comp.TileSizeHalfVector, + grid.Comp.TileSizeVector), + MarkerColor); + anyVacuum = true; + } + } + } + }, + // This clears the buffer to all zero first... + EmptyColor); + + // no distortion, no need to render + if (!anyVacuum) + { + args.WorldHandle.UseShader(null); + args.WorldHandle.SetTransform(Matrix3x2.Identity); + return false; + } + + return true; + } + + protected override void Draw(in OverlayDrawArgs args) + { + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + + if (ScreenTexture is null || res.VacuumTarget is null) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + + args.WorldHandle.UseShader(_shader); + args.WorldHandle.DrawTextureRect(res.VacuumTarget.Texture, args.WorldBounds); + + args.WorldHandle.UseShader(null); + args.WorldHandle.SetTransform(Matrix3x2.Identity); + } + + protected override void DisposeBehavior() + { + _resources.Dispose(); + + _configManager.UnsubValueChanged(CCVars.VacuumOverlayIntensity, SetVacuumOverlayIntensity); + base.DisposeBehavior(); + } + + internal sealed class CachedResources : IDisposable + { + public IRenderTexture? VacuumTarget; + + public void Dispose() + { + VacuumTarget?.Dispose(); + } + } +} diff --git a/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs b/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs index 65d0b919d0..6f46fee98c 100644 --- a/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs @@ -204,9 +204,6 @@ public sealed partial class GasTileVisibleGasOverlay : Overlay return true; }); - - drawHandle.UseShader(null); - drawHandle.SetTransform(Matrix3x2.Identity); } private void DrawMapOverlay( diff --git a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml index 160b6fa281..4d93f3d4e5 100644 --- a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml +++ b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml @@ -1,7 +1,8 @@ - - + @@ -43,4 +44,4 @@ - + diff --git a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs index 3680c82a55..0c78344348 100644 --- a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs +++ b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs @@ -15,7 +15,7 @@ using Direction = Robust.Shared.Maths.Direction; namespace Content.Client.Atmos.UI { [GenerateTypedNameReferences] - public sealed partial class GasAnalyzerWindow : DefaultWindow + public sealed partial class GasAnalyzerWindow : FancyWindow { private readonly SharedAtmosphereSystem _atmosphere; private NetEntity _currentEntity = NetEntity.Invalid; diff --git a/Content.Client/Atmos/UI/GasCanisterWindow.xaml b/Content.Client/Atmos/UI/GasCanisterWindow.xaml index 3c7647385a..576685060e 100644 --- a/Content.Client/Atmos/UI/GasCanisterWindow.xaml +++ b/Content.Client/Atmos/UI/GasCanisterWindow.xaml @@ -1,8 +1,9 @@ - - + - + diff --git a/Content.Client/Atmos/UI/GasCanisterWindow.xaml.cs b/Content.Client/Atmos/UI/GasCanisterWindow.xaml.cs index b21aa737ac..2638d91c74 100644 --- a/Content.Client/Atmos/UI/GasCanisterWindow.xaml.cs +++ b/Content.Client/Atmos/UI/GasCanisterWindow.xaml.cs @@ -1,4 +1,5 @@ using System; +using Content.Client.UserInterface.Controls; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -13,7 +14,7 @@ namespace Content.Client.Atmos.UI /// Client-side UI used to control a canister. /// [GenerateTypedNameReferences] - public sealed partial class GasCanisterWindow : DefaultWindow + public sealed partial class GasCanisterWindow : FancyWindow { private readonly ButtonGroup _buttonGroup = new(); diff --git a/Content.Client/Atmos/UI/GasFilterWindow.xaml b/Content.Client/Atmos/UI/GasFilterWindow.xaml index 5077fddc61..a5a12c5d1d 100644 --- a/Content.Client/Atmos/UI/GasFilterWindow.xaml +++ b/Content.Client/Atmos/UI/GasFilterWindow.xaml @@ -1,8 +1,8 @@ - - + - + diff --git a/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs b/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs index fb5d1e871e..afe7290b79 100644 --- a/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs +++ b/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Content.Client.UserInterface.Controls; using Content.Shared.Atmos.Prototypes; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; @@ -14,7 +15,7 @@ namespace Content.Client.Atmos.UI /// Client-side UI used to control a gas filter. /// [GenerateTypedNameReferences] - public sealed partial class GasFilterWindow : DefaultWindow + public sealed partial class GasFilterWindow : FancyWindow { private readonly ButtonGroup _buttonGroup = new(); diff --git a/Content.Client/Atmos/UI/GasMixerWindow.xaml b/Content.Client/Atmos/UI/GasMixerWindow.xaml index c36b53798a..8de57a2103 100644 --- a/Content.Client/Atmos/UI/GasMixerWindow.xaml +++ b/Content.Client/Atmos/UI/GasMixerWindow.xaml @@ -1,8 +1,8 @@ - - + Resizable="False" Title="Gas Mixer"> +