diff --git a/Content.Client/Administration/UI/ManageSolutions/AddReagentWindow.xaml.cs b/Content.Client/Administration/UI/ManageSolutions/AddReagentWindow.xaml.cs index cf7c8c5058..1ded5cebb3 100644 --- a/Content.Client/Administration/UI/ManageSolutions/AddReagentWindow.xaml.cs +++ b/Content.Client/Administration/UI/ManageSolutions/AddReagentWindow.xaml.cs @@ -4,9 +4,6 @@ using Robust.Client.Console; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Prototypes; namespace Content.Client.Administration.UI.ManageSolutions diff --git a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs index d2dda0ccbd..ecc26d6026 100644 --- a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs +++ b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs @@ -20,7 +20,7 @@ namespace Content.Client.Administration.UI.ManageSolutions private NetEntity _target = NetEntity.Invalid; private string? _selectedSolution; private AddReagentWindow? _addReagentWindow; - private Dictionary? _solutions; + private Dictionary? _solutions; public EditSolutionsWindow() { @@ -60,9 +60,11 @@ namespace Content.Client.Administration.UI.ManageSolutions if (_selectedSolution == null || _solutions == null) return; - if (!_solutions.TryGetValue(_selectedSolution, out var solution)) + if (!_solutions.TryGetValue(_selectedSolution, out var solutionId) || + !_entityManager.TryGetComponent(solutionId, out SolutionComponent? solutionComp)) return; + var solution = solutionComp.Solution; UpdateVolumeBox(solution); UpdateThermalBox(solution); @@ -198,10 +200,13 @@ namespace Content.Client.Administration.UI.ManageSolutions /// private void SetReagent(FloatSpinBox.FloatSpinBoxEventArgs args, string prototype) { - if (_solutions == null || _selectedSolution == null) + if (_solutions == null || _selectedSolution == null || + !_solutions.TryGetValue(_selectedSolution, out var solutionId) || + !_entityManager.TryGetComponent(solutionId, out SolutionComponent? solutionComp)) return; - var current = _solutions[_selectedSolution].GetTotalPrototypeQuantity(prototype); + var solution = solutionComp.Solution; + var current = solution.GetTotalPrototypeQuantity(prototype); var delta = args.Value - current.Float(); if (MathF.Abs(delta) < 0.01) @@ -275,22 +280,38 @@ namespace Content.Client.Administration.UI.ManageSolutions /// /// Update the solution options. /// - public void UpdateSolutions(Dictionary? solutions) + public void UpdateSolutions(List<(string, NetEntity)>? solutions) { SolutionOption.Clear(); - _solutions = solutions; + + if (solutions is { Count: > 0 }) + { + if (_solutions is { Count: > 0 }) + _solutions.Clear(); + else + _solutions = new(solutions.Count); + + foreach (var (name, netSolution) in solutions) + { + if (_entityManager.TryGetEntity(netSolution, out var solution)) + _solutions.Add(name, solution.Value); + } + } + else + _solutions = null; if (_solutions == null) return; int i = 0; - foreach (var solution in _solutions.Keys) + int selectedIndex = 0; // Default to the first solution if none are found. + foreach (var (name, _) in _solutions) { - SolutionOption.AddItem(solution, i); - SolutionOption.SetItemMetadata(i, solution); + SolutionOption.AddItem(name, i); + SolutionOption.SetItemMetadata(i, name); - if (solution == _selectedSolution) - SolutionOption.Select(i); + if (name == _selectedSolution) + selectedIndex = i; i++; } @@ -300,14 +321,11 @@ namespace Content.Client.Administration.UI.ManageSolutions // No applicable solutions Close(); Dispose(); + return; } - if (_selectedSolution == null || !_solutions.ContainsKey(_selectedSolution)) - { - // the previously selected solution is no longer valid. - SolutionOption.Select(0); - _selectedSolution = (string?) SolutionOption.SelectedMetadata; - } + SolutionOption.Select(selectedIndex); + _selectedSolution = (string?) SolutionOption.SelectedMetadata; } } } diff --git a/Content.Client/Audio/AudioUIController.cs b/Content.Client/Audio/AudioUIController.cs new file mode 100644 index 0000000000..ef903672fd --- /dev/null +++ b/Content.Client/Audio/AudioUIController.cs @@ -0,0 +1,98 @@ +using Content.Shared.CCVar; +using Robust.Client.Audio; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Configuration; + +namespace Content.Client.Audio; + +public sealed class AudioUIController : UIController +{ + [Dependency] private readonly IAudioManager _audioManager = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IResourceCache _cache = default!; + + private float _interfaceGain; + private IAudioSource? _clickSource; + private IAudioSource? _hoverSource; + + private const float ClickGain = 0.25f; + private const float HoverGain = 0.05f; + + public override void Initialize() + { + base.Initialize(); + + /* + * This exists to load UI sounds outside of the game sim. + */ + + // No unsub coz never shuts down until program exit. + _configManager.OnValueChanged(CCVars.UIClickSound, SetClickSound, true); + _configManager.OnValueChanged(CCVars.UIHoverSound, SetHoverSound, true); + + _configManager.OnValueChanged(CCVars.InterfaceVolume, SetInterfaceVolume, true); + } + + private void SetInterfaceVolume(float obj) + { + _interfaceGain = obj; + + if (_clickSource != null) + { + _clickSource.Gain = ClickGain * _interfaceGain; + } + + if (_hoverSource != null) + { + _hoverSource.Gain = HoverGain * _interfaceGain; + } + } + + private void SetClickSound(string value) + { + if (!string.IsNullOrEmpty(value)) + { + var resource = _cache.GetResource(value); + var source = + _audioManager.CreateAudioSource(resource); + + if (source != null) + { + source.Gain = ClickGain * _interfaceGain; + source.Global = true; + } + + _clickSource = source; + UIManager.SetClickSound(source); + } + else + { + UIManager.SetClickSound(null); + } + } + + private void SetHoverSound(string value) + { + if (!string.IsNullOrEmpty(value)) + { + var hoverResource = _cache.GetResource(value); + var hoverSource = + _audioManager.CreateAudioSource(hoverResource); + + if (hoverSource != null) + { + hoverSource.Gain = HoverGain * _interfaceGain; + hoverSource.Global = true; + } + + _hoverSource = hoverSource; + UIManager.SetHoverSound(hoverSource); + } + else + { + UIManager.SetHoverSound(null); + } + } +} diff --git a/Content.Client/Audio/ContentAudioSystem.cs b/Content.Client/Audio/ContentAudioSystem.cs index dd1670592e..37ede1158b 100644 --- a/Content.Client/Audio/ContentAudioSystem.cs +++ b/Content.Client/Audio/ContentAudioSystem.cs @@ -1,15 +1,18 @@ using Content.Shared.Audio; -using Content.Shared.CCVar; using Content.Shared.GameTicking; -using Robust.Client.GameObjects; -using Robust.Shared; -using Robust.Shared.Audio; +using Robust.Client.Audio; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; using AudioComponent = Robust.Shared.Audio.Components.AudioComponent; namespace Content.Client.Audio; public sealed partial class ContentAudioSystem : SharedContentAudioSystem { + [Dependency] private readonly IAudioManager _audioManager = default!; + [Dependency] private readonly IResourceCache _cache = default!; + [Dependency] private readonly IUserInterfaceManager _uiManager = default!; + // Need how much volume to change per tick and just remove it when it drops below "0" private readonly Dictionary _fadingOut = new(); @@ -32,6 +35,7 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem public const float AmbienceMultiplier = 3f; public const float AmbientMusicMultiplier = 3f; public const float LobbyMultiplier = 3f; + public const float InterfaceMultiplier = 2f; public const float TtsMultiplier = 3f; // Corvax-TTS public override void Initialize() diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index db5fa2bd99..8536c3c429 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -1,6 +1,7 @@ using Content.Client.Rotation; using Content.Shared.Buckle; using Content.Shared.Buckle.Components; +using Content.Shared.Rotation; using Content.Shared.Vehicle.Components; using Robust.Client.GameObjects; @@ -56,17 +57,15 @@ internal sealed class BuckleSystem : SharedBuckleSystem if (!TryComp(uid, out var rotVisuals)) return; - if (!Appearance.TryGetData(uid, StrapVisuals.RotationAngle, out var angle, args.Component) || - !Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) || + if (!Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) || !buckled || args.Sprite == null) { - _rotationVisualizerSystem.SetHorizontalAngle(uid, rotVisuals.DefaultRotation, rotVisuals); + _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation); return; } // Animate strapping yourself to something at a given angle - _rotationVisualizerSystem.SetHorizontalAngle(uid, Angle.FromDegrees(angle), rotVisuals); // TODO: Dump this when buckle is better _rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f); } diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs new file mode 100644 index 0000000000..d28d3228c9 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs @@ -0,0 +1,28 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.UserInterface; + +namespace Content.Client.CartridgeLoader.Cartridges; + +public sealed partial class LogProbeUi : UIFragment +{ + private LogProbeUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner) + { + _fragment = new LogProbeUiFragment(); + } + + public override void UpdateState(BoundUserInterfaceState state) + { + if (state is not LogProbeUiState logProbeUiState) + return; + + _fragment?.UpdateState(logProbeUiState.PulledLogs); + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml new file mode 100644 index 0000000000..5712b301c3 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml @@ -0,0 +1,20 @@ + + + + + diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml.cs new file mode 100644 index 0000000000..369042d991 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml.cs @@ -0,0 +1,17 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class LogProbeUiEntry : BoxContainer +{ + public LogProbeUiEntry(int numberLabel, string timeText, string accessorText) + { + RobustXamlLoader.Load(this); + NumberLabel.Text = numberLabel.ToString(); + TimeLabel.Text = timeText; + AccessorLabel.Text = accessorText; + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml new file mode 100644 index 0000000000..d369a33c6c --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs new file mode 100644 index 0000000000..b22e0bc196 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs @@ -0,0 +1,39 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class LogProbeUiFragment : BoxContainer +{ + public LogProbeUiFragment() + { + RobustXamlLoader.Load(this); + } + + public void UpdateState(List logs) + { + ProbedDeviceContainer.RemoveAllChildren(); + + //Reverse the list so the oldest entries appear at the bottom + logs.Reverse(); + + var count = 1; + foreach (var log in logs) + { + AddAccessLog(log, count); + count++; + } + } + + private void AddAccessLog(PulledAccessLog log, int numberLabelText) + { + var timeLabelText = TimeSpan.FromSeconds(Math.Truncate(log.Time.TotalSeconds)).ToString(); + var accessorLabelText = log.Accessor; + var entry = new LogProbeUiEntry(numberLabelText, timeLabelText, accessorLabelText); + + ProbedDeviceContainer.AddChild(entry); + } +} diff --git a/Content.Client/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs b/Content.Client/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs new file mode 100644 index 0000000000..0c38d605d2 --- /dev/null +++ b/Content.Client/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Chemistry.EntitySystems; + +namespace Content.Client.Chemistry.Containers.EntitySystems; + +public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem +{ +} diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs index 44a24595ba..20693408ae 100644 --- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs +++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Chemistry; +using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Rounding; using Robust.Client.GameObjects; diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs index 28cf3ba16c..9a09436176 100644 --- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs +++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs @@ -394,7 +394,7 @@ namespace Content.Client.Construction.UI if (IsAtFront) { WindowOpen = false; - _uiManager.GetActiveUIWidget().CraftingButton.Pressed = false; // This does not call CraftingButtonToggled + _uiManager.GetActiveUIWidget().CraftingButton.SetClickPressed(false); // This does not call CraftingButtonToggled } else _constructionView.MoveToFront(); @@ -402,7 +402,7 @@ namespace Content.Client.Construction.UI else { WindowOpen = true; - _uiManager.GetActiveUIWidget().CraftingButton.Pressed = true; // This does not call CraftingButtonToggled + _uiManager.GetActiveUIWidget().CraftingButton.SetClickPressed(true); // This does not call CraftingButtonToggled } } diff --git a/Content.Client/Explosion/ExplosionOverlaySystem.cs b/Content.Client/Explosion/ExplosionOverlaySystem.cs index 064b068a97..35716616da 100644 --- a/Content.Client/Explosion/ExplosionOverlaySystem.cs +++ b/Content.Client/Explosion/ExplosionOverlaySystem.cs @@ -3,6 +3,7 @@ using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.GameStates; using Robust.Shared.Graphics.RSI; +using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -18,6 +19,7 @@ public sealed class ExplosionOverlaySystem : EntitySystem [Dependency] private readonly IResourceCache _resCache = default!; [Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly SharedPointLightSystem _lights = default!; + [Dependency] private readonly IMapManager _mapMan = default!; public override void Initialize() { @@ -65,15 +67,20 @@ public sealed class ExplosionOverlaySystem : EntitySystem return; } - // spawn in a client-side light source at the epicenter - var lightEntity = Spawn("ExplosionLight", component.Epicenter); - var light = _lights.EnsureLight(lightEntity); + // Map may have been deleted. + if (_mapMan.MapExists(component.Epicenter.MapId)) + { + // spawn in a client-side light source at the epicenter + var lightEntity = Spawn("ExplosionLight", component.Epicenter); + var light = _lights.EnsureLight(lightEntity); + + _lights.SetRadius(lightEntity, component.Intensity.Count, light); + _lights.SetEnergy(lightEntity, component.Intensity.Count, light); + _lights.SetColor(lightEntity, type.LightColor, light); + textures.LightEntity = lightEntity; + } - _lights.SetRadius(lightEntity, component.Intensity.Count, light); - _lights.SetEnergy(lightEntity, component.Intensity.Count, light); - _lights.SetColor(lightEntity, type.LightColor, light); - textures.LightEntity = lightEntity; textures.FireColor = type.FireColor; textures.IntensityPerState = type.IntensityPerState; diff --git a/Content.Client/Eye/Blinding/BlindOverlay.cs b/Content.Client/Eye/Blinding/BlindOverlay.cs index 83c6cd23e6..1730db59c6 100644 --- a/Content.Client/Eye/Blinding/BlindOverlay.cs +++ b/Content.Client/Eye/Blinding/BlindOverlay.cs @@ -1,9 +1,12 @@ +using Content.Client.Movement.Systems; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.Enums; using Robust.Shared.Prototypes; using Content.Shared.Eye.Blinding; using Content.Shared.Eye.Blinding.Components; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Systems; namespace Content.Client.Eye.Blinding { @@ -11,9 +14,8 @@ namespace Content.Client.Eye.Blinding { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] IEntityManager _entityManager = default!; - [Dependency] ILightManager _lightManager = default!; - + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly ILightManager _lightManager = default!; public override bool RequestScreenTexture => true; public override OverlaySpace Space => OverlaySpace.WorldSpace; @@ -30,13 +32,13 @@ namespace Content.Client.Eye.Blinding } protected override bool BeforeDraw(in OverlayDrawArgs args) { - if (!_entityManager.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out EyeComponent? eyeComp)) + if (!_entityManager.TryGetComponent(_playerManager.LocalSession?.AttachedEntity, out EyeComponent? eyeComp)) return false; if (args.Viewport.Eye != eyeComp.Eye) return false; - var playerEntity = _playerManager.LocalPlayer?.ControlledEntity; + var playerEntity = _playerManager.LocalSession?.AttachedEntity; if (playerEntity == null) return false; @@ -64,6 +66,11 @@ namespace Content.Client.Eye.Blinding if (ScreenTexture == null) return; + var playerEntity = _playerManager.LocalSession?.AttachedEntity; + + if (playerEntity == null) + return; + if (!_blindableComponent.GraceFrame) { _blindableComponent.LightSetup = true; // Ok we touched the lights @@ -73,6 +80,10 @@ namespace Content.Client.Eye.Blinding _blindableComponent.GraceFrame = false; } + if (_entityManager.TryGetComponent(playerEntity, out var content)) + { + _circleMaskShader?.SetParameter("ZOOM", content.Zoom.X); + } _greyscaleShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); var worldHandle = args.WorldHandle; diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs index 1822dd7fff..6e4b7a7618 100644 --- a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs +++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs @@ -1,11 +1,9 @@ using Content.Client.UserInterface.Controls; -using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Kitchen; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; @@ -15,7 +13,7 @@ namespace Content.Client.Kitchen.UI public sealed partial class GrinderMenu : FancyWindow { private readonly IEntityManager _entityManager; - private readonly IPrototypeManager _prototypeManager ; + private readonly IPrototypeManager _prototypeManager; private readonly ReagentGrinderBoundUserInterface _owner; private readonly Dictionary _chamberVisualContents = new(); @@ -122,8 +120,8 @@ namespace Content.Client.Kitchen.UI { foreach (var (reagent, quantity) in reagents) { - var reagentName = _prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto) - ? Loc.GetString($"{quantity} {proto.LocalizedName}") + var reagentName = _prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto) + ? Loc.GetString($"{quantity} {proto.LocalizedName}") : "???"; BeakerContentBox.BoxContents.AddItem(reagentName); } diff --git a/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs b/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs index 518fbe095e..74579b2699 100644 --- a/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs +++ b/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs @@ -1,11 +1,9 @@ -using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Kitchen.Components; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; -using Robust.Shared.Graphics; namespace Content.Client.Kitchen.UI { diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml b/Content.Client/Options/UI/Tabs/AudioTab.xaml index 7d271d02aa..08b4575b31 100644 --- a/Content.Client/Options/UI/Tabs/AudioTab.xaml +++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml @@ -74,6 +74,19 @@