Skip to content

Commit

Permalink
Random Announcer System (Simple-Station#415)
Browse files Browse the repository at this point in the history
# Description

Replaces every instance of station announcements with an announcer
system meant to handle audio and messages for various announcers defined
in prototypes instead of each server replacing the scattered files
inconsistently with whatever singular thing they want to hear announce
messages.

# TODO

- [x] Systems
- [x] CVars
- [x] Sounds
- [x] Client volume slider
- [x] Collections
- [x] Prototypes
- [x] Events
- [x] Commands
- [x] PR media
- [x] Deglobalize
- [x] Passthrough localization parameters to overrides
- [x] Make every announcer follow the template
- [x] Move sounds into subdirectories
- [x] Make announcement IDs camelCased
- [x] Test announcement localizations
- [x] Weighted announcer lists

---

<details><summary><h1>Media</h1></summary>
<p>


https://github.com/Simple-Station/Parkstation-Friendly-Chainsaw/assets/77995199/caf5805d-acb0-4140-b344-875a8f79e5ee

</p>
</details>

---

# Changelog

:cl:
- add: Added 4 new announcers that will randomly be selected every shift
  • Loading branch information
DEATHB4DEFEAT committed Jul 12, 2024
1 parent 98bfbde commit e3bc8d4
Show file tree
Hide file tree
Showing 192 changed files with 2,123 additions and 272 deletions.
69 changes: 69 additions & 0 deletions Content.Client/Announcements/Systems/AnnouncerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Content.Client.Audio;
using Content.Shared.Announcements.Events;
using Content.Shared.Announcements.Systems;
using Content.Shared.CCVar;
using Robust.Client.Audio;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;

namespace Content.Client.Announcements.Systems;

public sealed class AnnouncerSystem : SharedAnnouncerSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly IAudioManager _audioManager = default!;

private IAudioSource? AnnouncerSource { get; set; }
private float AnnouncerVolume { get; set; }


public override void Initialize()
{
base.Initialize();

AnnouncerVolume = _config.GetCVar(CCVars.AnnouncerVolume) * 100f / ContentAudioSystem.AnnouncerMultiplier;

SubscribeNetworkEvent<AnnouncementSendEvent>(OnAnnouncementReceived);
_config.OnValueChanged(CCVars.AnnouncerVolume, OnAnnouncerVolumeChanged);
}

public override void Shutdown()
{
base.Shutdown();

_config.UnsubValueChanged(CCVars.AnnouncerVolume, OnAnnouncerVolumeChanged);
}


private void OnAnnouncerVolumeChanged(float value)
{
AnnouncerVolume = value;

if (AnnouncerSource != null)
AnnouncerSource.Gain = AnnouncerVolume;
}

private void OnAnnouncementReceived(AnnouncementSendEvent ev)
{
if (!ev.Recipients.Contains(_player.LocalSession!.UserId)
|| !_cache.TryGetResource<AudioResource>(GetAnnouncementPath(ev.AnnouncementId, ev.AnnouncerId),
out var resource))
return;

var source = _audioManager.CreateAudioSource(resource);
if (source != null)
{
source.Gain = AnnouncerVolume * SharedAudioSystem.VolumeToGain(ev.AudioParams.Volume);
source.Global = true;
}

AnnouncerSource?.Dispose();
AnnouncerSource = source;
AnnouncerSource?.StartPlaying();
}
}
3 changes: 2 additions & 1 deletion Content.Client/Audio/ContentAudioSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
public const float AmbientMusicMultiplier = 3f;
public const float LobbyMultiplier = 3f;
public const float InterfaceMultiplier = 2f;

public const float AnnouncerMultiplier = 3f;

public override void Initialize()
{
base.Initialize();
Expand Down
13 changes: 13 additions & 0 deletions Content.Client/Options/UI/Tabs/AudioTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@
<Label Name="AmbienceSoundsLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-announcer-volume'}" HorizontalExpand="True" />
<Control MinSize="8 0" />
<Slider Name="AnnouncerVolumeSlider"
MinValue="0"
MaxValue="100"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="AnnouncerVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<Control MinSize="0 8" />
<CheckBox Name="LobbyMusicCheckBox" Text="{Loc 'ui-options-lobby-music'}" />
<CheckBox Name="RestartSoundsCheckBox" Text="{Loc 'ui-options-restart-sounds'}" />
Expand Down
18 changes: 16 additions & 2 deletions Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public AudioTab()
AmbienceSoundsSlider.OnValueChanged += OnAmbienceSoundsSliderChanged;
LobbyVolumeSlider.OnValueChanged += OnLobbyVolumeSliderChanged;
InterfaceVolumeSlider.OnValueChanged += OnInterfaceVolumeSliderChanged;
AnnouncerVolumeSlider.OnValueChanged += OnAnnouncerVolumeSliderChanged;
LobbyMusicCheckBox.OnToggled += OnLobbyMusicCheckToggled;
RestartSoundsCheckBox.OnToggled += OnRestartSoundsCheckToggled;
EventMusicCheckBox.OnToggled += OnEventMusicCheckToggled;
Expand All @@ -58,6 +59,7 @@ protected override void Dispose(bool disposing)
AmbienceVolumeSlider.OnValueChanged -= OnAmbienceVolumeSliderChanged;
LobbyVolumeSlider.OnValueChanged -= OnLobbyVolumeSliderChanged;
InterfaceVolumeSlider.OnValueChanged -= OnInterfaceVolumeSliderChanged;
AnnouncerVolumeSlider.OnValueChanged -= OnAnnouncerVolumeSliderChanged;
base.Dispose(disposing);
}

Expand Down Expand Up @@ -97,6 +99,11 @@ private void OnMidiVolumeSliderChanged(Range range)
UpdateChanges();
}

private void OnAnnouncerVolumeSliderChanged(Range range)
{
UpdateChanges();
}

private void OnLobbyMusicCheckToggled(BaseButton.ButtonEventArgs args)
{
UpdateChanges();
Expand Down Expand Up @@ -125,6 +132,7 @@ private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
_cfg.SetCVar(CCVars.AmbientMusicVolume, AmbientMusicVolumeSlider.Value / 100f * ContentAudioSystem.AmbientMusicMultiplier);
_cfg.SetCVar(CCVars.LobbyMusicVolume, LobbyVolumeSlider.Value / 100f * ContentAudioSystem.LobbyMultiplier);
_cfg.SetCVar(CCVars.InterfaceVolume, InterfaceVolumeSlider.Value / 100f * ContentAudioSystem.InterfaceMultiplier);
_cfg.SetCVar(CCVars.AnnouncerVolume, AnnouncerVolumeSlider.Value / 100f * ContentAudioSystem.AnnouncerMultiplier);

_cfg.SetCVar(CCVars.MaxAmbientSources, (int)AmbienceSoundsSlider.Value);

Expand All @@ -149,6 +157,7 @@ private void Reset()
AmbientMusicVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier;
LobbyVolumeSlider.Value = _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier;
InterfaceVolumeSlider.Value = _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier;
AnnouncerVolumeSlider.Value = _cfg.GetCVar(CCVars.AnnouncerVolume) * 100f / ContentAudioSystem.AnnouncerMultiplier;

AmbienceSoundsSlider.Value = _cfg.GetCVar(CCVars.MaxAmbientSources);

Expand All @@ -174,14 +183,17 @@ private void UpdateChanges()
Math.Abs(LobbyVolumeSlider.Value - _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier) < 0.01f;
var isInterfaceVolumeSame =
Math.Abs(InterfaceVolumeSlider.Value - _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier) < 0.01f;
var isAnnouncerVolumeSame =
Math.Abs(AnnouncerVolumeSlider.Value - _cfg.GetCVar(CCVars.AnnouncerVolume) * 100f / ContentAudioSystem.AnnouncerMultiplier) < 0.01f;

var isAmbientSoundsSame = (int)AmbienceSoundsSlider.Value == _cfg.GetCVar(CCVars.MaxAmbientSources);
var isLobbySame = LobbyMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.LobbyMusicEnabled);
var isRestartSoundsSame = RestartSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.RestartSoundsEnabled);
var isEventSame = EventMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.EventMusicEnabled);
var isAdminSoundsSame = AdminSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.AdminSoundsEnabled);
var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
&& isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame;
var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame
&& isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
&& isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame && isAnnouncerVolumeSame;
ApplyButton.Disabled = isEverythingSame;
ResetButton.Disabled = isEverythingSame;
MasterVolumeLabel.Text =
Expand All @@ -196,6 +208,8 @@ private void UpdateChanges()
Loc.GetString("ui-options-volume-percent", ("volume", LobbyVolumeSlider.Value / 100));
InterfaceVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", InterfaceVolumeSlider.Value / 100));
AnnouncerVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", AnnouncerVolumeSlider.Value / 100));
AmbienceSoundsLabel.Text = ((int)AmbienceSoundsSlider.Value).ToString();
}
}
Expand Down
41 changes: 41 additions & 0 deletions Content.IntegrationTests/Tests/Announcers/AnnouncerFallbackTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Announcements.Prototypes;
using Robust.Shared.Prototypes;

namespace Content.IntegrationTests.Tests.Announcers;

[TestFixture]
[TestOf(typeof(AnnouncerPrototype))]
public sealed class AnnouncerPrototypeTests
{
[Test]
public async Task TestAnnouncerFallbacks()
{
// Checks if every announcer has a fallback announcement

await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;

var prototype = server.ResolveDependency<IPrototypeManager>();

await server.WaitAssertion(() =>
{
var success = true;
var why = new List<string>();
foreach (var announcer in prototype.EnumeratePrototypes<AnnouncerPrototype>())
{
if (announcer.Announcements.All(a => a.ID.ToLower() != "fallback"))
{
success = false;
why.Add(announcer.ID);
}
}
Assert.That(success, Is.True, $"The following announcers do not have a fallback announcement:\n {string.Join("\n ", why)}");
});

await pair.CleanReturnAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using Content.Server.Announcements.Systems;
using Content.Server.StationEvents;
using Content.Shared.Announcements.Prototypes;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;

namespace Content.IntegrationTests.Tests.Announcers;

[TestFixture]
[TestOf(typeof(AnnouncerPrototype))]
public sealed class AnnouncerLocalizationTest
{
[Test]
public async Task TestEventLocalization()
{
// Checks if every station event wanting the announcerSystem to send messages has a localization string
// If an event doesn't have startAnnouncement or endAnnouncement set to true
// it will be expected for that system to handle the announcements if it wants them

await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;

var locale = server.ResolveDependency<ILocalizationManager>();
var entSysMan = server.ResolveDependency<IEntitySystemManager>();
var announcer = entSysMan.GetEntitySystem<AnnouncerSystem>();
var events = entSysMan.GetEntitySystem<EventManagerSystem>();

await server.WaitAssertion(() =>
{
var succeeded = true;
var why = new List<string>();
foreach (var ev in events.AllEvents())
{
if (ev.Value.StartAnnouncement)
{
var announcementId = announcer.GetAnnouncementId(ev.Key.ID);
var eventLocaleString = announcer.GetEventLocaleString(announcementId);
if (locale.GetString(eventLocaleString) == eventLocaleString)
{
succeeded = false;
why.Add($"\"{announcementId}\": \"{eventLocaleString}\"");
}
}
if (ev.Value.EndAnnouncement)
{
var announcementId = announcer.GetAnnouncementId(ev.Key.ID, true);
var eventLocaleString = announcer.GetEventLocaleString(announcementId);
if (locale.GetString(eventLocaleString) == eventLocaleString)
{
succeeded = false;
why.Add($"\"{announcementId}\": \"{eventLocaleString}\"");
}
}
}
Assert.That(succeeded, Is.True, $"The following announcements do not have a localization string:\n {string.Join("\n ", why)}");
});

await pair.CleanReturnAsync();
}
}
8 changes: 7 additions & 1 deletion Content.Server/Administration/UI/AdminAnnounceEui.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@
using Content.Server.EUI;
using Content.Shared.Administration;
using Content.Shared.Eui;
using Content.Server.Announcements.Systems;
using Robust.Shared.Player;

namespace Content.Server.Administration.UI
{
public sealed class AdminAnnounceEui : BaseEui
{
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
private readonly AnnouncerSystem _announcer;
private readonly ChatSystem _chatSystem;

public AdminAnnounceEui()
{
IoCManager.InjectDependencies(this);

_announcer = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AnnouncerSystem>();
_chatSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>();
}

Expand Down Expand Up @@ -50,7 +55,8 @@ public override void HandleMessage(EuiMessageBase msg)
break;
// TODO: Per-station announcement support
case AdminAnnounceType.Station:
_chatSystem.DispatchGlobalAnnouncement(doAnnounce.Announcement, doAnnounce.Announcer, colorOverride: Color.Gold);
_announcer.SendAnnouncement(_announcer.GetAnnouncementId("Announce"), Filter.Broadcast(),
doAnnounce.Announcement, doAnnounce.Announcer, Color.Gold);
break;
}

Expand Down
Loading

0 comments on commit e3bc8d4

Please sign in to comment.