diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 7ff9017cb0..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,4 +0,0 @@ -contact_links: - - name: Report a Security Vulnerability - url: https://github.com/space-wizards/space-station-14/blob/master/SECURITY.md - about: Please report security vulnerabilities to the Space Wizards privately so they can fix them before they are publicly disclosed. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index aba549332f..51f9de8baf 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,8 +1,8 @@ --- -name: Request a Feature -about: "Template for noting future planned features. Please ask for approval in the Discord if you aren't an organization Member before posting a feature request" +name: Request a feature +about: "Please outline your request in Discord first if you aren't a maintainer." title: '' -labels: '' +labels: ["Type: Feature"] assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/issue_report.md b/.github/ISSUE_TEMPLATE/issue_report.md index ab82181197..c74f24554a 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.md +++ b/.github/ISSUE_TEMPLATE/issue_report.md @@ -1,8 +1,8 @@ --- -name: Report an Issue -about: "Any general issues you have during play or with the codebase" +name: Report an issue +about: "Any issues found in gameplay or the codebase" title: '' -labels: '' +labels: 'Type: Bug' assignees: '' --- diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 877273d764..83b20ddd6f 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -8,6 +8,7 @@ env: GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} CHANGELOG_DIR: ${{ vars.CHANGELOG_DIR }} PR_NUMBER: ${{ github.event.number }} + CHANGELOG_WEBHOOK: ${{ secrets.CHANGELOG_WEBHOOK }} jobs: changelog: @@ -54,3 +55,7 @@ jobs: git push shell: bash continue-on-error: true + + - name: Publish changelog + run: Tools/actions_changelogs_since_last_run.py + continue-on-error: true diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index b992e77256..90158ba81e 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -65,6 +65,7 @@ private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponen return; component.Whitelist = state.Whitelist; + component.Blacklist = state.Blacklist; component.CanTargetSelf = state.CanTargetSelf; BaseHandleState(uid, component, state); } diff --git a/Content.Client/Announcements/Systems/AnnouncerSystem.cs b/Content.Client/Announcements/Systems/AnnouncerSystem.cs index de76396f70..2ce419b788 100644 --- a/Content.Client/Announcements/Systems/AnnouncerSystem.cs +++ b/Content.Client/Announcements/Systems/AnnouncerSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Client.Audio; using Content.Shared.Announcements.Events; using Content.Shared.Announcements.Systems; @@ -18,8 +19,8 @@ public sealed class AnnouncerSystem : SharedAnnouncerSystem [Dependency] private readonly IResourceCache _cache = default!; [Dependency] private readonly IAudioManager _audioManager = default!; - private IAudioSource? AnnouncerSource { get; set; } - private float AnnouncerVolume { get; set; } + public List AnnouncerSources { get; } = new(); + public float AnnouncerVolume { get; private set; } public override void Initialize() @@ -28,8 +29,10 @@ public override void Initialize() AnnouncerVolume = _config.GetCVar(CCVars.AnnouncerVolume) * 100f / ContentAudioSystem.AnnouncerMultiplier; - SubscribeNetworkEvent(OnAnnouncementReceived); _config.OnValueChanged(CCVars.AnnouncerVolume, OnAnnouncerVolumeChanged); + _config.OnValueChanged(CCVars.AnnouncerDisableMultipleSounds, OnAnnouncerDisableMultipleSounds); + + SubscribeNetworkEvent(OnAnnouncementReceived); } public override void Shutdown() @@ -37,6 +40,7 @@ public override void Shutdown() base.Shutdown(); _config.UnsubValueChanged(CCVars.AnnouncerVolume, OnAnnouncerVolumeChanged); + _config.UnsubValueChanged(CCVars.AnnouncerDisableMultipleSounds, OnAnnouncerDisableMultipleSounds); } @@ -44,10 +48,23 @@ private void OnAnnouncerVolumeChanged(float value) { AnnouncerVolume = value; - if (AnnouncerSource != null) - AnnouncerSource.Gain = AnnouncerVolume; + foreach (var source in AnnouncerSources) + source.Gain = AnnouncerVolume; } + private void OnAnnouncerDisableMultipleSounds(bool value) + { + if (!value) + return; + + foreach (var audioSource in AnnouncerSources.ToList()) + { + audioSource.Dispose(); + AnnouncerSources.Remove(audioSource); + } + } + + private void OnAnnouncementReceived(AnnouncementSendEvent ev) { if (!ev.Recipients.Contains(_player.LocalSession!.UserId) @@ -56,14 +73,28 @@ private void OnAnnouncementReceived(AnnouncementSendEvent ev) return; var source = _audioManager.CreateAudioSource(resource); - if (source != null) + if (source == null) + return; + + source.Gain = AnnouncerVolume * SharedAudioSystem.VolumeToGain(ev.AudioParams.Volume); + source.Global = true; + + if (_config.GetCVar(CCVars.AnnouncerDisableMultipleSounds)) + { + foreach (var audioSource in AnnouncerSources.ToList()) + { + audioSource.Dispose(); + AnnouncerSources.Remove(audioSource); + } + } + + foreach (var audioSource in AnnouncerSources.ToList().Where(audioSource => !audioSource.Playing)) { - source.Gain = AnnouncerVolume * SharedAudioSystem.VolumeToGain(ev.AudioParams.Volume); - source.Global = true; + audioSource.Dispose(); + AnnouncerSources.Remove(audioSource); } - AnnouncerSource?.Dispose(); - AnnouncerSource = source; - AnnouncerSource?.StartPlaying(); + AnnouncerSources.Add(source); + source.StartPlaying(); } } diff --git a/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs b/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs new file mode 100644 index 0000000000..072730d65d --- /dev/null +++ b/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs @@ -0,0 +1,119 @@ +using Content.Shared.Audio.Jukebox; +using Robust.Client.Audio; +using Robust.Client.Player; +using Robust.Shared.Audio.Components; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Client.Audio.Jukebox; + +public sealed class JukeboxBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; + + [ViewVariables] + private JukeboxMenu? _menu; + + public JukeboxBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _menu = new JukeboxMenu(); + _menu.OnClose += Close; + _menu.OpenCentered(); + + _menu.OnPlayPressed += args => + { + if (args) + { + SendMessage(new JukeboxPlayingMessage()); + } + else + { + SendMessage(new JukeboxPauseMessage()); + } + }; + + _menu.OnStopPressed += () => + { + SendMessage(new JukeboxStopMessage()); + }; + + _menu.OnSongSelected += SelectSong; + + _menu.SetTime += SetTime; + PopulateMusic(); + Reload(); + } + + /// + /// Reloads the attached menu if it exists. + /// + public void Reload() + { + if (_menu == null || !EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox)) + return; + + _menu.SetAudioStream(jukebox.AudioStream); + + if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto)) + { + var length = EntMan.System().GetAudioLength(songProto.Path.Path.ToString()); + _menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds); + } + else + { + _menu.SetSelectedSong(string.Empty, 0f); + } + } + + public void PopulateMusic() + { + _menu?.Populate(_protoManager.EnumeratePrototypes()); + } + + public void SelectSong(ProtoId songid) + { + SendMessage(new JukeboxSelectedMessage(songid)); + } + + public void SetTime(float time) + { + var sentTime = time; + + // You may be wondering, what the fuck is this + // Well we want to be able to predict the playback slider change, of which there are many ways to do it + // We can't just use SendPredictedMessage because it will reset every tick and audio updates every frame + // so it will go BRRRRT + // Using ping gets us close enough that it SHOULD, MOST OF THE TIME, fall within the 0.1 second tolerance + // that's still on engine so our playback position never gets corrected. + if (EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox) && + EntMan.TryGetComponent(jukebox.AudioStream, out AudioComponent? audioComp)) + { + audioComp.PlaybackPosition = time; + } + + SendMessage(new JukeboxSetTimeMessage(sentTime)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + if (_menu == null) + return; + + _menu.OnClose -= Close; + _menu.Dispose(); + _menu = null; + } +} + diff --git a/Content.Client/Audio/Jukebox/JukeboxMenu.xaml b/Content.Client/Audio/Jukebox/JukeboxMenu.xaml new file mode 100644 index 0000000000..e8d39a9b11 --- /dev/null +++ b/Content.Client/Audio/Jukebox/JukeboxMenu.xaml @@ -0,0 +1,18 @@ + + + + + + +