diff --git a/Content.Client/_DV/Salvage/UI/MiningVoucherBoundUserInterface.cs b/Content.Client/_DV/Salvage/UI/MiningVoucherBoundUserInterface.cs new file mode 100644 index 00000000000..5ac6423f879 --- /dev/null +++ b/Content.Client/_DV/Salvage/UI/MiningVoucherBoundUserInterface.cs @@ -0,0 +1,27 @@ +using Content.Shared._DV.Salvage; +using Robust.Client.UserInterface; + +namespace Content.Client._DV.Salvage.UI; + +public sealed class MiningVoucherBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private MiningVoucherMenu? _menu; + + public MiningVoucherBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _menu = this.CreateWindow(); + _menu.SetEntity(Owner); + _menu.OnSelected += i => + { + SendMessage(new MiningVoucherSelectMessage(i)); + Close(); + }; + } +} diff --git a/Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml b/Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml new file mode 100644 index 00000000000..b1e27528a62 --- /dev/null +++ b/Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml @@ -0,0 +1,6 @@ + + + diff --git a/Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml.cs b/Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml.cs new file mode 100644 index 00000000000..ac37ec55450 --- /dev/null +++ b/Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml.cs @@ -0,0 +1,59 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared._DV.Salvage.Components; +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.Prototypes; +using System.Numerics; + +namespace Content.Client._DV.Salvage.UI; + +[GenerateTypedNameReferences] +public sealed partial class MiningVoucherMenu : RadialMenu +{ + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + + private readonly SpriteSystem _sprite; + + public event Action? OnSelected; + + public MiningVoucherMenu() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + + _sprite = _entMan.System(); + } + + public void SetEntity(EntityUid owner) + { + if (!_entMan.TryGetComponent(owner, out var comp)) + return; + + for (int i = 0; i < comp.Kits.Count; i++) + { + var index = i; // copy so the closure doesn't borrow it + var kit = _proto.Index(comp.Kits[i]); + var button = new RadialMenuTextureButton() + { + StyleClasses = { "RadialMenuTextureButton" }, + SetSize = new Vector2(64f, 64f), + ToolTip = Loc.GetString(kit.Description) + }; + button.AddChild(new TextureRect() + { + VerticalAlignment = VAlignment.Center, + HorizontalAlignment = HAlignment.Center, + Texture = _sprite.Frame0(kit.Sprite), + TextureScale = new Vector2(2f, 2f) + }); + + button.OnPressed += _ => OnSelected?.Invoke(index); + + Main.AddChild(button); + } + } +} diff --git a/Content.Client/_Lavaland/Aggression/AggressorsSystem.cs b/Content.Client/_Lavaland/Aggression/AggressorsSystem.cs new file mode 100644 index 00000000000..dbbc1b6567d --- /dev/null +++ b/Content.Client/_Lavaland/Aggression/AggressorsSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._Lavaland.Aggression; + +namespace Content.Client._Lavaland.Aggression; + +public sealed class AggressorsSystem : SharedAggressorsSystem; diff --git a/Content.Client/_Lavaland/Audio/BossMusicSystem.cs b/Content.Client/_Lavaland/Audio/BossMusicSystem.cs new file mode 100644 index 00000000000..b3e40a545b7 --- /dev/null +++ b/Content.Client/_Lavaland/Audio/BossMusicSystem.cs @@ -0,0 +1,240 @@ +using Content.Client.Audio; +using Content.Shared._Lavaland.Audio; +using Content.Shared.CCVar; +using Content.Shared.GameTicking; +using Content.Shared.Mobs; +using Robust.Client.Audio; +using Robust.Client.Player; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Components; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Configuration; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client._Lavaland.Audio; + +public sealed class BossMusicSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly ContentAudioSystem _audioContent = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private static float _volumeSlider; + private Entity? _bossMusicStream; + private BossMusicPrototype? _musicProto; + + // Need how much volume to change per tick and just remove it when it drops below "0" + private readonly Dictionary _fadingOut = new(); + + // Need volume change per tick + target volume. + private readonly Dictionary _fadingIn = new(); + + private readonly List _fadeToRemove = new(); + + private const float MinVolume = -32f; + private const float DefaultDuration = 2f; + + public override void Initialize() + { + base.Initialize(); + + Subs.CVar(_configManager, CCVars.LobbyMusicVolume, BossVolumeCVarChanged, true); + + SubscribeNetworkEvent(OnBossInit); + SubscribeNetworkEvent(OnBossDefeated); + + SubscribeLocalEvent(OnMindRemoved); + SubscribeLocalEvent(OnPlayerDeath); + SubscribeLocalEvent(OnPlayerParentChange); + SubscribeLocalEvent(OnRoundEnd); + } + + public override void Shutdown() + { + base.Shutdown(); + _bossMusicStream = _audio.Stop(_bossMusicStream); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_timing.IsFirstTimePredicted) + return; + + UpdateFade(frameTime); + } + + private void BossVolumeCVarChanged(float obj) + { + _volumeSlider = SharedAudioSystem.GainToVolume(obj); + + if (_bossMusicStream != null && _musicProto != null) + { + _audio.SetVolume(_bossMusicStream, _musicProto.Sound.Params.Volume + _volumeSlider); + } + } + + private void OnBossInit(BossMusicStartupEvent args) + { + if (_musicProto != null || _bossMusicStream != null) + return; + + _audioContent.DisableAmbientMusic(); + + var sound = _proto.Index(args.MusicId); + _musicProto = sound; + + var strim = _audio.PlayGlobal( + sound.Sound, + Filter.Local(), + false, + AudioParams.Default.WithVolume(sound.Sound.Params.Volume + _volumeSlider).WithLoop(true)); + + + if (_musicProto.FadeIn && strim != null) + { + _bossMusicStream = (strim.Value.Entity, strim.Value.Component); + FadeIn(_bossMusicStream, strim.Value.Component, sound.FadeInTime); + } + } + + private void OnBossDefeated(BossMusicStopEvent args) + { + EndAllMusic(); + } + + private void OnMindRemoved(LocalPlayerDetachedEvent args) + { + EndAllMusic(); + } + + private void OnPlayerDeath(Entity ent, ref MobStateChangedEvent args) + { + if (ent.Comp.PlayerSession == _player.LocalSession && + args.NewMobState == MobState.Dead) + EndAllMusic(); + } + + /// + /// Raised when salvager escapes from lavaland (ohio reference) + /// + private void OnPlayerParentChange(Entity ent, ref EntParentChangedMessage args) + { + if (ent.Comp.PlayerSession == _player.LocalSession && + args.OldMapId != null) + EndAllMusic(); + } + + private void OnRoundEnd(RoundEndMessageEvent args) + { + _bossMusicStream = _audio.Stop(_bossMusicStream); + } + + private void EndAllMusic() + { + if (_musicProto == null || _bossMusicStream == null) + return; + + if (_musicProto.FadeIn) + { + + FadeOut(_bossMusicStream, duration: _musicProto.FadeOutTime); + } + else + { + _audio.Stop(_bossMusicStream); + } + + _musicProto = null; + _bossMusicStream = null; + } + + #region Fades + + private void FadeOut(EntityUid? stream, AudioComponent? component = null, float duration = DefaultDuration) + { + if (stream == null || duration <= 0f || !Resolve(stream.Value, ref component)) + return; + + // Just in case + // TODO: Maybe handle the removals by making it seamless? + _fadingIn.Remove(stream.Value); + var diff = component.Volume - MinVolume; + _fadingOut.Add(stream.Value, diff / duration); + } + + private void FadeIn(EntityUid? stream, AudioComponent? component = null, float duration = DefaultDuration) + { + if (stream == null || duration <= 0f || !Resolve(stream.Value, ref component) || component.Volume < MinVolume) + return; + + _fadingOut.Remove(stream.Value); + var curVolume = component.Volume; + var change = (MinVolume - curVolume) / duration; + _fadingIn.Add(stream.Value, (change, component.Volume)); + component.Volume = MinVolume; + } + + private void UpdateFade(float frameTime) + { + _fadeToRemove.Clear(); + + foreach (var (stream, change) in _fadingOut) + { + if (!TryComp(stream, out AudioComponent? component)) + { + _fadeToRemove.Add(stream); + continue; + } + + var volume = component.Volume - change * frameTime; + volume = MathF.Max(MinVolume, volume); + _audio.SetVolume(stream, volume, component); + + if (component.Volume.Equals(MinVolume)) + { + _audio.Stop(stream); + _fadeToRemove.Add(stream); + } + } + + foreach (var stream in _fadeToRemove) + { + _fadingOut.Remove(stream); + } + + _fadeToRemove.Clear(); + + foreach (var (stream, (change, target)) in _fadingIn) + { + // Cancelled elsewhere + if (!TryComp(stream, out AudioComponent? component)) + { + _fadeToRemove.Add(stream); + continue; + } + + var volume = component.Volume - change * frameTime; + volume = MathF.Min(target, volume); + _audio.SetVolume(stream, volume, component); + + if (component.Volume.Equals(target)) + { + _fadeToRemove.Add(stream); + } + } + + foreach (var stream in _fadeToRemove) + { + _fadingIn.Remove(stream); + } + } + + #endregion +} diff --git a/Content.Client/_Lavaland/Mobs/DamageSquareSystem.cs b/Content.Client/_Lavaland/Mobs/DamageSquareSystem.cs new file mode 100644 index 00000000000..c897f7f814c --- /dev/null +++ b/Content.Client/_Lavaland/Mobs/DamageSquareSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._Lavaland.Damage; + +namespace Content.Client._Lavaland.Mobs; + +public sealed class DamageSquareSystem : SharedDamageSquareSystem; diff --git a/Content.Client/_Lavaland/Shuttles/Systems/DockingConsoleSystem.cs b/Content.Client/_Lavaland/Shuttles/Systems/DockingConsoleSystem.cs new file mode 100644 index 00000000000..a0bd521b34d --- /dev/null +++ b/Content.Client/_Lavaland/Shuttles/Systems/DockingConsoleSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._Lavaland.Shuttles.Systems; + +namespace Content.Client._Lavaland.Shuttles.Systems; + +public sealed class DockingConsoleSystem : SharedDockingConsoleSystem; diff --git a/Content.Client/_Lavaland/Shuttles/UI/DockingConsoleBoundUserInterface.cs b/Content.Client/_Lavaland/Shuttles/UI/DockingConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..80b652279c8 --- /dev/null +++ b/Content.Client/_Lavaland/Shuttles/UI/DockingConsoleBoundUserInterface.cs @@ -0,0 +1,39 @@ +using Content.Shared._Lavaland.Shuttles; + +namespace Content.Client._Lavaland.Shuttles.UI; + +public sealed class DockingConsoleBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private DockingConsoleWindow? _window; + + public DockingConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _window = new DockingConsoleWindow(Owner); + _window.OnFTL += index => SendMessage(new DockingConsoleFTLMessage(index)); + _window.OnShuttleCall += args => SendMessage(new DockingConsoleShuttleCheckMessage()); + _window.OnClose += Close; + _window.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (state is DockingConsoleState cast) + _window?.UpdateState(cast); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + _window?.Orphan(); + } +} diff --git a/Content.Client/_Lavaland/Shuttles/UI/DockingConsoleWindow.xaml b/Content.Client/_Lavaland/Shuttles/UI/DockingConsoleWindow.xaml new file mode 100644 index 00000000000..0f379427c9e --- /dev/null +++ b/Content.Client/_Lavaland/Shuttles/UI/DockingConsoleWindow.xaml @@ -0,0 +1,19 @@ + + + + + + + +