From 3da454140db308f918bcbd8cab73649601ae4f3a Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 23 Jun 2022 20:11:03 +1000 Subject: [PATCH] Add department-specific radio channels (#9061) * Add department-specific radio channels This commit adds working department-specific radio channels, while minimizing damage to the current codebase. It is expected that a future refactor will clean this up a bit. ChatSystem now has a RadioPrefix() method that recognizes department-specific channels (e.g. ":e" and ":m") in addition to the global channel (";"). It strips the prefix from the message and assigns messages an integer representing the destination channel, if any. IListen and IRadio now accept optional 'channel' arguments with this channel in mind. The ugly is that the integer channel number is hard-coded and also shows up in chat. Comms are not modeled at this time. You cannot break comms (yet). All headsets have channels soldered into them. You cannot change encryption keys to hop on new channels. Steal a headset instead. * Remove debugging print * Convert to prototypes * Use prototype names in headset prototype * Adjust list style * Document prototype fields * cringe * some cleanup * colours * Remove alphas at least * cc Co-authored-by: Kevin Zheng --- Content.Server/Actions/ActionsSystem.cs | 1 + .../Administration/Commands/DSay.cs | 1 + .../Administration/UI/AdminAnnounceEui.cs | 1 + Content.Server/Advertise/AdvertiseSystem.cs | 1 + Content.Server/AlertLevel/AlertLevelSystem.cs | 1 + .../Announcements/AnnounceCommand.cs | 1 + Content.Server/Chat/Commands/LOOCCommand.cs | 1 + Content.Server/Chat/Commands/MeCommand.cs | 1 + Content.Server/Chat/Commands/SayCommand.cs | 1 + .../Chat/Commands/WhisperCommand.cs | 3 +- .../Chat/Systems/ChatSystem.Radio.cs | 88 +++++++++++++++++++ .../Chat/{ => Systems}/ChatSystem.cs | 48 +++------- .../CommunicationsConsoleSystem.cs | 1 + Content.Server/Disease/DiseaseSystem.cs | 1 + Content.Server/GameTicking/GameTicker.cs | 1 + .../Ghost/Components/GhostRadioComponent.cs | 26 +++--- Content.Server/Headset/HeadsetComponent.cs | 59 +++++++------ Content.Server/Headset/HeadsetSystem.cs | 19 +++- Content.Server/Nuke/NukeCodeSystem.cs | 1 + Content.Server/Nuke/NukeSystem.cs | 1 + .../Components/HandheldRadioComponent.cs | 35 +++++--- Content.Server/Radio/Components/IListen.cs | 6 +- Content.Server/Radio/Components/IRadio.cs | 8 +- .../Radio/EntitySystems/ListeningSystem.cs | 8 +- .../Radio/EntitySystems/RadioSystem.cs | 10 +-- Content.Server/Roles/Job.cs | 1 + Content.Server/RoundEnd/RoundEndSystem.cs | 1 + Content.Server/Salvage/SalvageSystem.cs | 1 + Content.Server/Speech/AccentSystem.cs | 1 + Content.Server/Speech/SpeechNoiseSystem.cs | 1 + .../Station/Systems/StationSystem.cs | 1 + .../StationEvents/Events/DiseaseOutbreak.cs | 1 + .../StationEvents/Events/RandomSentience.cs | 1 + .../StationEvents/Events/StationEvent.cs | 1 + .../StationEvents/Events/ZombieOutbreak.cs | 1 + Content.Shared/Radio/RadioChannelPrototype.cs | 26 ++++++ .../en-US/chat/managers/chat-manager.ftl | 1 + .../en-US/headset/headset-component.ftl | 3 +- .../Entities/Clothing/Ears/headsets.yml | 50 +++++++++++ .../Entities/Clothing/Ears/headsets_alt.yml | 22 +++++ Resources/Prototypes/radio_channels.yml | 66 ++++++++++++++ 41 files changed, 397 insertions(+), 105 deletions(-) create mode 100644 Content.Server/Chat/Systems/ChatSystem.Radio.cs rename Content.Server/Chat/{ => Systems}/ChatSystem.cs (94%) create mode 100644 Content.Shared/Radio/RadioChannelPrototype.cs create mode 100644 Resources/Prototypes/radio_channels.yml diff --git a/Content.Server/Actions/ActionsSystem.cs b/Content.Server/Actions/ActionsSystem.cs index a0f6133eb9..98a800b99b 100644 --- a/Content.Server/Actions/ActionsSystem.cs +++ b/Content.Server/Actions/ActionsSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; using JetBrains.Annotations; diff --git a/Content.Server/Administration/Commands/DSay.cs b/Content.Server/Administration/Commands/DSay.cs index 37517acddf..79b0e8db7d 100644 --- a/Content.Server/Administration/Commands/DSay.cs +++ b/Content.Server/Administration/Commands/DSay.cs @@ -1,4 +1,5 @@ using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Console; diff --git a/Content.Server/Administration/UI/AdminAnnounceEui.cs b/Content.Server/Administration/UI/AdminAnnounceEui.cs index 18e074d3f5..2a42c0ac8a 100644 --- a/Content.Server/Administration/UI/AdminAnnounceEui.cs +++ b/Content.Server/Administration/UI/AdminAnnounceEui.cs @@ -1,6 +1,7 @@ using Content.Server.Administration.Managers; using Content.Server.Chat; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Server.EUI; using Content.Shared.Administration; using Content.Shared.Eui; diff --git a/Content.Server/Advertise/AdvertiseSystem.cs b/Content.Server/Advertise/AdvertiseSystem.cs index 05faeb4be2..f07765b687 100644 --- a/Content.Server/Advertise/AdvertiseSystem.cs +++ b/Content.Server/Advertise/AdvertiseSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Advertisements; using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Server.Power.Components; using Content.Server.VendingMachines; using Robust.Shared.Prototypes; diff --git a/Content.Server/AlertLevel/AlertLevelSystem.cs b/Content.Server/AlertLevel/AlertLevelSystem.cs index 717cef52b9..d99f16b852 100644 --- a/Content.Server/AlertLevel/AlertLevelSystem.cs +++ b/Content.Server/AlertLevel/AlertLevelSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Server.Station.Systems; using Robust.Shared.Audio; using Robust.Shared.Player; diff --git a/Content.Server/Announcements/AnnounceCommand.cs b/Content.Server/Announcements/AnnounceCommand.cs index c5eeaa23bf..d51ea19ab0 100644 --- a/Content.Server/Announcements/AnnounceCommand.cs +++ b/Content.Server/Announcements/AnnounceCommand.cs @@ -1,5 +1,6 @@ using Content.Server.Administration; using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Shared.Administration; using Robust.Shared.Console; diff --git a/Content.Server/Chat/Commands/LOOCCommand.cs b/Content.Server/Chat/Commands/LOOCCommand.cs index 98df2a26e3..a897a0b2bf 100644 --- a/Content.Server/Chat/Commands/LOOCCommand.cs +++ b/Content.Server/Chat/Commands/LOOCCommand.cs @@ -1,3 +1,4 @@ +using Content.Server.Chat.Systems; using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Console; diff --git a/Content.Server/Chat/Commands/MeCommand.cs b/Content.Server/Chat/Commands/MeCommand.cs index 6d205712ec..dc87d38245 100644 --- a/Content.Server/Chat/Commands/MeCommand.cs +++ b/Content.Server/Chat/Commands/MeCommand.cs @@ -1,3 +1,4 @@ +using Content.Server.Chat.Systems; using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Console; diff --git a/Content.Server/Chat/Commands/SayCommand.cs b/Content.Server/Chat/Commands/SayCommand.cs index 8aa157c31b..889ce77d2f 100644 --- a/Content.Server/Chat/Commands/SayCommand.cs +++ b/Content.Server/Chat/Commands/SayCommand.cs @@ -1,3 +1,4 @@ +using Content.Server.Chat.Systems; using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Console; diff --git a/Content.Server/Chat/Commands/WhisperCommand.cs b/Content.Server/Chat/Commands/WhisperCommand.cs index 6c7dbb7838..04fa397735 100644 --- a/Content.Server/Chat/Commands/WhisperCommand.cs +++ b/Content.Server/Chat/Commands/WhisperCommand.cs @@ -1,4 +1,5 @@ -using Content.Shared.Administration; +using Content.Server.Chat.Systems; +using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.Enums; diff --git a/Content.Server/Chat/Systems/ChatSystem.Radio.cs b/Content.Server/Chat/Systems/ChatSystem.Radio.cs new file mode 100644 index 0000000000..ef97ad83b1 --- /dev/null +++ b/Content.Server/Chat/Systems/ChatSystem.Radio.cs @@ -0,0 +1,88 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Content.Server.Headset; +using Content.Shared.Radio; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.Chat.Systems; + +public sealed partial class ChatSystem +{ + /// + /// Cache of the keycodes for faster lookup. + /// + private Dictionary _keyCodes = new(); + + private void InitializeRadio() + { + _prototypeManager.PrototypesReloaded += OnPrototypeReload; + CacheRadios(); + } + + private void OnPrototypeReload(PrototypesReloadedEventArgs obj) + { + CacheRadios(); + } + + private void CacheRadios() + { + _keyCodes.Clear(); + + foreach (var proto in _prototypeManager.EnumeratePrototypes()) + { + _keyCodes.Add(proto.KeyCode, proto); + } + } + + private void ShutdownRadio() + { + _prototypeManager.PrototypesReloaded -= OnPrototypeReload; + } + + private (string, RadioChannelPrototype?) GetRadioPrefix(EntityUid source, string message) + { + // TODO: Turn common into a true frequency and support multiple aliases. + var channelMessage = message.StartsWith(':') || message.StartsWith('.'); + var radioMessage = message.StartsWith(';') || channelMessage; + if (!radioMessage) return (message, null); + + // Special case for empty messages + if (message.Length <= 1) + return (string.Empty, null); + + // Look for a prefix indicating a destination radio channel. + RadioChannelPrototype? chan; + if (channelMessage && message.Length >= 2) + { + _keyCodes.TryGetValue(message[1], out chan); + + if (chan == null) + { + _popup.PopupEntity(Loc.GetString("chat-manager-no-such-channel"), source, Filter.Entities(source)); + chan = null; + } + + // Strip message prefix. + message = message[2..].TrimStart(); + } + else + { + // Remove semicolon + message = message[1..].TrimStart(); + chan = _prototypeManager.Index("Common"); + } + + if (_inventory.TryGetSlotEntity(source, "ears", out var entityUid) && + TryComp(entityUid, out HeadsetComponent? headset)) + { + headset.RadioRequested = true; + } + else + { + _popup.PopupEntity(Loc.GetString("chat-manager-no-headset-on-message"), source, Filter.Entities(source)); + } + + return (message, chan); + } +} diff --git a/Content.Server/Chat/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs similarity index 94% rename from Content.Server/Chat/ChatSystem.cs rename to Content.Server/Chat/Systems/ChatSystem.cs index 9ca58abaff..648bfcf40f 100644 --- a/Content.Server/Chat/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -5,7 +5,6 @@ using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Ghost.Components; -using Content.Server.Headset; using Content.Server.Players; using Content.Server.Popups; using Content.Server.Radio.EntitySystems; @@ -23,22 +22,24 @@ using Robust.Shared.Console; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Players; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; -namespace Content.Server.Chat; +namespace Content.Server.Chat.Systems; /// /// ChatSystem is responsible for in-simulation chat handling, such as whispering, speaking, emoting, etc. /// ChatSystem depends on ChatManager to actually send the messages. /// -public sealed class ChatSystem : SharedChatSystem +public sealed partial class ChatSystem : SharedChatSystem { [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatSanitizationManager _sanitizer = default!; [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; @@ -56,6 +57,7 @@ public sealed class ChatSystem : SharedChatSystem public override void Initialize() { + InitializeRadio(); _configurationManager.OnValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged, true); SubscribeLocalEvent(OnGameChange); @@ -63,6 +65,7 @@ public sealed class ChatSystem : SharedChatSystem public override void Shutdown() { + ShutdownRadio(); _configurationManager.UnsubValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged); } @@ -226,14 +229,18 @@ public sealed class ChatSystem : SharedChatSystem if (!_actionBlocker.CanSpeak(source)) return; message = TransformSpeech(source, message); - _listener.PingListeners(source, message); + (message, var channel) = GetRadioPrefix(source, message); + + if (channel != null) + _listener.PingListeners(source, message, channel); + var messageWrap = Loc.GetString("chat-manager-entity-say-wrap-message", ("entityName", Name(source))); SendInVoiceRange(ChatChannel.Local, message, messageWrap, source, hideChat); var ev = new EntitySpokeEvent(message); - RaiseLocalEvent(source, ev, false); + RaiseLocalEvent(source, ev); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Say from {ToPrettyString(source):user}: {message}"); } @@ -242,7 +249,6 @@ public sealed class ChatSystem : SharedChatSystem if (!_actionBlocker.CanSpeak(source)) return; message = TransformSpeech(source, message); - _listener.PingListeners(source, message); var obfuscatedMessage = ObfuscateMessageReadability(message, 0.2f); var transformSource = Transform(source); @@ -410,34 +416,8 @@ public sealed class ChatSystem : SharedChatSystem private string SanitizeMessageCapital(EntityUid source, string message) { - if (message.StartsWith(';')) - { - // Special case for ";" messages - if (message.Length == 1) - return ""; - - // Remove semicolon - message = message.Substring(1).TrimStart(); - - // Capitalize first letter - message = message[0].ToString().ToUpper() + message.Remove(0, 1); - - if (_inventory.TryGetSlotEntity(source, "ears", out var entityUid) && - TryComp(entityUid, out HeadsetComponent? headset)) - { - headset.RadioRequested = true; - } - else - { - _popup.PopupEntity(Loc.GetString("chat-manager-no-headset-on-message"), source, Filter.Entities(source)); - } - } - else - { - // Capitalize first letter - message = message[0].ToString().ToUpper() + message.Remove(0, 1); - } - + // Capitalize first letter + message = message[0].ToString().ToUpper() + message.Remove(0, 1); return message; } diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index e43c6ebd39..30303e9bbc 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -2,6 +2,7 @@ using System.Globalization; using Content.Server.Access.Systems; using Content.Server.AlertLevel; using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Server.Popups; using Content.Server.RoundEnd; using Content.Server.Station.Systems; diff --git a/Content.Server/Disease/DiseaseSystem.cs b/Content.Server/Disease/DiseaseSystem.cs index 3e4dc03aa8..071460817e 100644 --- a/Content.Server/Disease/DiseaseSystem.cs +++ b/Content.Server/Disease/DiseaseSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Disease.Components; using Content.Server.Disease.Components; using Content.Server.Clothing.Components; using Content.Server.Body.Systems; +using Content.Server.Chat.Systems; using Content.Shared.MobState.Components; using Content.Shared.Examine; using Content.Shared.Inventory; diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 6963de8342..bdb4d2e8a1 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -2,6 +2,7 @@ using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Chat; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Server.Database; using Content.Server.Ghost; using Content.Server.Maps; diff --git a/Content.Server/Ghost/Components/GhostRadioComponent.cs b/Content.Server/Ghost/Components/GhostRadioComponent.cs index 5c5cef9671..981ff49775 100644 --- a/Content.Server/Ghost/Components/GhostRadioComponent.cs +++ b/Content.Server/Ghost/Components/GhostRadioComponent.cs @@ -1,7 +1,9 @@ using Content.Server.Radio.Components; using Content.Shared.Chat; +using Content.Shared.Radio; using Robust.Server.GameObjects; using Robust.Shared.Network; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; namespace Content.Server.Ghost.Components { @@ -12,27 +14,27 @@ namespace Content.Server.Ghost.Components [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IEntityManager _entMan = default!; - [DataField("channels")] - private List _channels = new(){1459}; + [DataField("channels", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + private HashSet _channels = new(); - public IReadOnlyList Channels => _channels; - - public void Receive(string message, int channel, EntityUid speaker) + public void Receive(string message, RadioChannelPrototype channel, EntityUid speaker) { - if (!_entMan.TryGetComponent(Owner, out ActorComponent? actor)) + if (!_channels.Contains(channel.ID) || !_entMan.TryGetComponent(Owner, out ActorComponent? actor)) return; var playerChannel = actor.PlayerSession.ConnectedClient; - var msg = new MsgChatMessage(); + var msg = new MsgChatMessage + { + Channel = ChatChannel.Radio, + Message = message, + //Square brackets are added here to avoid issues with escaping + MessageWrap = Loc.GetString("chat-radio-message-wrap", ("channel", $"\\[{channel.Name}\\]"), ("name", _entMan.GetComponent(speaker).EntityName)) + }; - msg.Channel = ChatChannel.Radio; - msg.Message = message; - //Square brackets are added here to avoid issues with escaping - msg.MessageWrap = Loc.GetString("chat-radio-message-wrap", ("channel", $"\\[{channel}\\]"), ("name", _entMan.GetComponent(speaker).EntityName)); _netManager.ServerSendMessage(msg, playerChannel); } - public void Broadcast(string message, EntityUid speaker) { } + public void Broadcast(string message, EntityUid speaker, RadioChannelPrototype channel) { } } } diff --git a/Content.Server/Headset/HeadsetComponent.cs b/Content.Server/Headset/HeadsetComponent.cs index 64da4609a9..30e316d46f 100644 --- a/Content.Server/Headset/HeadsetComponent.cs +++ b/Content.Server/Headset/HeadsetComponent.cs @@ -1,9 +1,13 @@ using Content.Server.Radio.Components; using Content.Server.Radio.EntitySystems; using Content.Shared.Chat; +using Content.Shared.Radio; using Robust.Server.GameObjects; using Robust.Shared.Containers; +using Robust.Shared.Prototypes; using Robust.Shared.Network; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; namespace Content.Server.Headset { @@ -19,19 +23,16 @@ namespace Content.Server.Headset private RadioSystem _radioSystem = default!; - [DataField("channels")] - private List _channels = new(){1459}; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("broadcastChannel")] - public int BroadcastFrequency { get; set; } = 1459; + [DataField("channels", customTypeSerializer:typeof(PrototypeIdHashSetSerializer))] + public HashSet Channels = new() + { + "Common" + }; [ViewVariables(VVAccess.ReadWrite)] [DataField("listenRange")] public int ListenRange { get; private set; } - public IReadOnlyList Channels => _channels; - public bool RadioRequested { get; set; } protected override void Initialize() @@ -41,38 +42,40 @@ namespace Content.Server.Headset _radioSystem = EntitySystem.Get(); } - public bool CanListen(string message, EntityUid source) + public bool CanListen(string message, EntityUid source, RadioChannelPrototype prototype) { - return RadioRequested; + return Channels.Contains(prototype.ID) && RadioRequested; } - public void Receive(string message, int channel, EntityUid source) + public void Receive(string message, RadioChannelPrototype channel, EntityUid source) { - if (Owner.TryGetContainer(out var container)) + if (!Channels.Contains(channel.ID) || !Owner.TryGetContainer(out var container)) return; + + if (!_entMan.TryGetComponent(container.Owner, out ActorComponent? actor)) return; + + var playerChannel = actor.PlayerSession.ConnectedClient; + + var msg = new MsgChatMessage { - if (!_entMan.TryGetComponent(container.Owner, out ActorComponent? actor)) - return; - - var playerChannel = actor.PlayerSession.ConnectedClient; - - var msg = new MsgChatMessage(); - - msg.Channel = ChatChannel.Radio; - msg.Message = message; + Channel = ChatChannel.Radio, + Message = message, //Square brackets are added here to avoid issues with escaping - msg.MessageWrap = Loc.GetString("chat-radio-message-wrap", ("channel", $"\\[{channel}\\]"), ("name", _entMan.GetComponent(source).EntityName)); - _netManager.ServerSendMessage(msg, playerChannel); - } + MessageWrap = Loc.GetString("chat-radio-message-wrap", ("color", channel.Color), ("channel", $"\\[{channel.Name}\\]"), ("name", _entMan.GetComponent(source).EntityName)) + }; + + _netManager.ServerSendMessage(msg, playerChannel); } - public void Listen(string message, EntityUid speaker) + public void Listen(string message, EntityUid speaker, RadioChannelPrototype channel) { - Broadcast(message, speaker); + Broadcast(message, speaker, channel); } - public void Broadcast(string message, EntityUid speaker) + public void Broadcast(string message, EntityUid speaker, RadioChannelPrototype channel) { - _radioSystem.SpreadMessage(this, speaker, message, BroadcastFrequency); + if (!Channels.Contains(channel.ID)) return; + + _radioSystem.SpreadMessage(this, speaker, message, channel); RadioRequested = false; } } diff --git a/Content.Server/Headset/HeadsetSystem.cs b/Content.Server/Headset/HeadsetSystem.cs index a4013166e2..4f9c81c06d 100644 --- a/Content.Server/Headset/HeadsetSystem.cs +++ b/Content.Server/Headset/HeadsetSystem.cs @@ -1,9 +1,13 @@ using Content.Shared.Examine; +using Content.Shared.Radio; +using Robust.Shared.Prototypes; namespace Content.Server.Headset { public sealed class HeadsetSystem : EntitySystem { + [Dependency] private readonly IPrototypeManager _protoManager = default!; + public override void Initialize() { base.Initialize(); @@ -14,8 +18,21 @@ namespace Content.Server.Headset { if (!args.IsInDetailsRange) return; - args.PushMarkup(Loc.GetString("examine-radio-frequency", ("frequency", component.BroadcastFrequency))); + // args.PushMarkup(Loc.GetString("examine-radio-frequency", ("frequency", component.BroadcastFrequency))); args.PushMarkup(Loc.GetString("examine-headset")); + + foreach (var id in component.Channels) + { + if (id == "Common") continue; + + var proto = _protoManager.Index(id); + args.PushMarkup(Loc.GetString("examine-headset-channel", + ("color", proto.Color), + ("key", proto.KeyCode), + ("id", proto.Name), + ("freq", proto.Frequency))); + } + args.PushMarkup(Loc.GetString("examine-headset-chat-prefix", ("prefix", ";"))); } } diff --git a/Content.Server/Nuke/NukeCodeSystem.cs b/Content.Server/Nuke/NukeCodeSystem.cs index 4a362eb902..80f2a42bf8 100644 --- a/Content.Server/Nuke/NukeCodeSystem.cs +++ b/Content.Server/Nuke/NukeCodeSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Server.Communications; using Content.Server.Station.Systems; using Content.Shared.GameTicking; diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index bd1f3ca3ca..174fdf5d28 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -1,6 +1,7 @@ using Content.Server.AlertLevel; using Content.Server.Chat; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Server.Coordinates.Helpers; using Content.Server.Explosion.EntitySystems; using Content.Server.Popups; diff --git a/Content.Server/Radio/Components/HandheldRadioComponent.cs b/Content.Server/Radio/Components/HandheldRadioComponent.cs index 0fe483913e..c914b575d6 100644 --- a/Content.Server/Radio/Components/HandheldRadioComponent.cs +++ b/Content.Server/Radio/Components/HandheldRadioComponent.cs @@ -1,7 +1,12 @@ using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Server.Radio.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Radio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; namespace Content.Server.Radio.Components { @@ -18,12 +23,16 @@ namespace Content.Server.Radio.Components private RadioSystem _radioSystem = default!; private bool _radioOn; - [DataField("channels")] - private List _channels = new(){1459}; + [DataField("channels", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + private HashSet _channels = new(); + public int BroadcastFrequency => IoCManager.Resolve() + .Index(BroadcastChannel).Frequency; + + // TODO: Assert in componentinit that channels has this. [ViewVariables(VVAccess.ReadWrite)] - [DataField("broadcastChannel")] - public int BroadcastFrequency { get; set; } = 1459; + [DataField("broadcastChannel", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string BroadcastChannel { get; set; } = "Common"; [ViewVariables(VVAccess.ReadWrite)] [DataField("listenRange")] public int ListenRange { get; private set; } = 7; @@ -38,8 +47,6 @@ namespace Content.Server.Radio.Components } } - [ViewVariables] public IReadOnlyList Channels => _channels; - protected override void Initialize() { base.Initialize(); @@ -66,28 +73,30 @@ namespace Content.Server.Radio.Components return true; } - public bool CanListen(string message, EntityUid source) + public bool CanListen(string message, EntityUid source, RadioChannelPrototype prototype) { + if (!_channels.Contains(prototype.ID)) return false; + return RadioOn && EntitySystem.Get().InRangeUnobstructed(Owner, source, range: ListenRange); } - public void Receive(string message, int channel, EntityUid speaker) + public void Receive(string message, RadioChannelPrototype channel, EntityUid speaker) { - if (RadioOn) + if (_channels.Contains(channel.ID) && RadioOn) { Speak(message); } } - public void Listen(string message, EntityUid speaker) + public void Listen(string message, EntityUid speaker, RadioChannelPrototype channel) { - Broadcast(message, speaker); + Broadcast(message, speaker, channel); } - public void Broadcast(string message, EntityUid speaker) + public void Broadcast(string message, EntityUid speaker, RadioChannelPrototype channel) { - _radioSystem.SpreadMessage(this, speaker, message, BroadcastFrequency); + _radioSystem.SpreadMessage(this, speaker, message, channel); } void IActivate.Activate(ActivateEventArgs eventArgs) diff --git a/Content.Server/Radio/Components/IListen.cs b/Content.Server/Radio/Components/IListen.cs index 0a46738c3d..1effcab101 100644 --- a/Content.Server/Radio/Components/IListen.cs +++ b/Content.Server/Radio/Components/IListen.cs @@ -1,3 +1,5 @@ +using Content.Shared.Radio; + namespace Content.Server.Radio.Components { /// @@ -8,8 +10,8 @@ namespace Content.Server.Radio.Components { int ListenRange { get; } - bool CanListen(string message, EntityUid source); + bool CanListen(string message, EntityUid source, RadioChannelPrototype channelPrototype); - void Listen(string message, EntityUid speaker); + void Listen(string message, EntityUid speaker, RadioChannelPrototype channel); } } diff --git a/Content.Server/Radio/Components/IRadio.cs b/Content.Server/Radio/Components/IRadio.cs index 07ece2cda2..a7cc0148c0 100644 --- a/Content.Server/Radio/Components/IRadio.cs +++ b/Content.Server/Radio/Components/IRadio.cs @@ -1,11 +1,11 @@ +using Content.Shared.Radio; + namespace Content.Server.Radio.Components { public interface IRadio : IComponent { - IReadOnlyList Channels { get; } + void Receive(string message, RadioChannelPrototype channel, EntityUid speaker); - void Receive(string message, int channel, EntityUid speaker); - - void Broadcast(string message, EntityUid speaker); + void Broadcast(string message, EntityUid speaker, RadioChannelPrototype channel); } } diff --git a/Content.Server/Radio/EntitySystems/ListeningSystem.cs b/Content.Server/Radio/EntitySystems/ListeningSystem.cs index 6783f1c530..08486ad7e6 100644 --- a/Content.Server/Radio/EntitySystems/ListeningSystem.cs +++ b/Content.Server/Radio/EntitySystems/ListeningSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Radio.Components; +using Content.Shared.Radio; using JetBrains.Annotations; namespace Content.Server.Radio.EntitySystems @@ -6,14 +7,15 @@ namespace Content.Server.Radio.EntitySystems [UsedImplicitly] public sealed class ListeningSystem : EntitySystem { - public void PingListeners(EntityUid source, string message) + public void PingListeners(EntityUid source, string message, RadioChannelPrototype channel) { foreach (var listener in EntityManager.EntityQuery(true)) { + // TODO: Listening code is hella stinky so please refactor it someone. // TODO: Map Position distance - if (listener.CanListen(message, source)) + if (listener.CanListen(message, source, channel)) { - listener.Listen(message, source); + listener.Listen(message, source, channel); } } } diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index 4136698723..8ce5460508 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Shared.Examine; using Content.Server.Radio.Components; +using Content.Shared.Radio; using JetBrains.Annotations; namespace Content.Server.Radio.EntitySystems @@ -23,7 +24,7 @@ namespace Content.Server.Radio.EntitySystems args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine",("frequency", component.BroadcastFrequency))); } - public void SpreadMessage(IRadio source, EntityUid speaker, string message, int channel) + public void SpreadMessage(IRadio source, EntityUid speaker, string message, RadioChannelPrototype channel) { if (_messages.Contains(message)) return; @@ -31,11 +32,8 @@ namespace Content.Server.Radio.EntitySystems foreach (var radio in EntityManager.EntityQuery(true)) { - if (radio.Channels.Contains(channel)) - { - //TODO: once voice identity gets added, pass into receiver via source.GetSpeakerVoice() - radio.Receive(message, channel, speaker); - } + //TODO: once voice identity gets added, pass into receiver via source.GetSpeakerVoice() + radio.Receive(message, channel, speaker); } _messages.Remove(message); diff --git a/Content.Server/Roles/Job.cs b/Content.Server/Roles/Job.cs index b66d9e3f54..e9da153659 100644 --- a/Content.Server/Roles/Job.cs +++ b/Content.Server/Roles/Job.cs @@ -1,5 +1,6 @@ using Content.Server.Chat; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Shared.Roles; namespace Content.Server.Roles diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index f528fe5540..abe96b3194 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -2,6 +2,7 @@ using System.Threading; using Content.Server.Administration.Logs; using Content.Server.Chat; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Server.GameTicking; using Content.Shared.Database; using Content.Shared.GameTicking; diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs index 212c6547f6..992bf5990b 100644 --- a/Content.Server/Salvage/SalvageSystem.cs +++ b/Content.Server/Salvage/SalvageSystem.cs @@ -13,6 +13,7 @@ using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Server.Station.Systems; namespace Content.Server.Salvage diff --git a/Content.Server/Speech/AccentSystem.cs b/Content.Server/Speech/AccentSystem.cs index c12871169a..13f174c01e 100644 --- a/Content.Server/Speech/AccentSystem.cs +++ b/Content.Server/Speech/AccentSystem.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using Content.Server.Chat; +using Content.Server.Chat.Systems; namespace Content.Server.Speech { diff --git a/Content.Server/Speech/SpeechNoiseSystem.cs b/Content.Server/Speech/SpeechNoiseSystem.cs index aacc987a3d..7c0a179a35 100644 --- a/Content.Server/Speech/SpeechNoiseSystem.cs +++ b/Content.Server/Speech/SpeechNoiseSystem.cs @@ -1,5 +1,6 @@ using Robust.Shared.Audio; using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Shared.Speech; using Robust.Shared.Player; using Robust.Shared.Prototypes; diff --git a/Content.Server/Station/Systems/StationSystem.cs b/Content.Server/Station/Systems/StationSystem.cs index aa061379ee..953c5c2c59 100644 --- a/Content.Server/Station/Systems/StationSystem.cs +++ b/Content.Server/Station/Systems/StationSystem.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Server.Chat; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Server.GameTicking; using Content.Server.Station.Components; using Content.Shared.CCVar; diff --git a/Content.Server/StationEvents/Events/DiseaseOutbreak.cs b/Content.Server/StationEvents/Events/DiseaseOutbreak.cs index 756fa3f912..34685bcff2 100644 --- a/Content.Server/StationEvents/Events/DiseaseOutbreak.cs +++ b/Content.Server/StationEvents/Events/DiseaseOutbreak.cs @@ -1,4 +1,5 @@ using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Server.Disease.Components; using Content.Server.Disease; using Content.Server.Station.Systems; diff --git a/Content.Server/StationEvents/Events/RandomSentience.cs b/Content.Server/StationEvents/Events/RandomSentience.cs index d9580d2979..1ed371651c 100644 --- a/Content.Server/StationEvents/Events/RandomSentience.cs +++ b/Content.Server/StationEvents/Events/RandomSentience.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Chat; +using Content.Server.Chat.Systems; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind.Commands; using Content.Server.Station.Systems; diff --git a/Content.Server/StationEvents/Events/StationEvent.cs b/Content.Server/StationEvents/Events/StationEvent.cs index e379aa9881..8218c29231 100644 --- a/Content.Server/StationEvents/Events/StationEvent.cs +++ b/Content.Server/StationEvents/Events/StationEvent.cs @@ -2,6 +2,7 @@ using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; using Content.Server.Chat; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Server.GameTicking; using Content.Server.Station.Components; using Content.Server.Station.Systems; diff --git a/Content.Server/StationEvents/Events/ZombieOutbreak.cs b/Content.Server/StationEvents/Events/ZombieOutbreak.cs index 1025aafaf8..b05d6c0c81 100644 --- a/Content.Server/StationEvents/Events/ZombieOutbreak.cs +++ b/Content.Server/StationEvents/Events/ZombieOutbreak.cs @@ -1,6 +1,7 @@ using Content.Server.Chat; using Robust.Shared.Random; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Server.Station.Systems; using Content.Shared.MobState.Components; using Content.Shared.Sound; diff --git a/Content.Shared/Radio/RadioChannelPrototype.cs b/Content.Shared/Radio/RadioChannelPrototype.cs new file mode 100644 index 0000000000..0348320212 --- /dev/null +++ b/Content.Shared/Radio/RadioChannelPrototype.cs @@ -0,0 +1,26 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Radio +{ + [Prototype("radioChannel")] + public sealed class RadioChannelPrototype : IPrototype + { + /// + /// Human-readable name for the channel. + /// + [ViewVariables] [DataField("name")] public string Name { get; private set; } = string.Empty; + + /// + /// Single-character prefix to determine what channel a message should be sent to. + /// + [ViewVariables] [DataField("keycode")] public char KeyCode { get; private set; } = '\0'; + + [ViewVariables] [DataField("frequency")] public int Frequency { get; private set; } = 0; + + [ViewVariables] [DataField("color")] public Color Color { get; private set; } = Color.Lime; + + [ViewVariables] + [IdDataFieldAttribute] + public string ID { get; } = default!; + } +} diff --git a/Resources/Locale/en-US/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/chat/managers/chat-manager.ftl index 7e4d3065e6..c555150fce 100644 --- a/Resources/Locale/en-US/chat/managers/chat-manager.ftl +++ b/Resources/Locale/en-US/chat/managers/chat-manager.ftl @@ -9,6 +9,7 @@ chat-manager-admin-ooc-chat-enabled-message = Admin OOC chat has been enabled. chat-manager-admin-ooc-chat-disabled-message = Admin OOC chat has been disabled. chat-manager-max-message-length-exceeded-message = Your message exceeded {$limit} character limit chat-manager-no-headset-on-message = You don't have a headset on! +chat-manager-no-such-channel = There is no such channel! chat-manager-whisper-headset-on-message = You can't whisper on the radio! chat-manager-server-wrap-message = SERVER: {"{0}"} chat-manager-sender-announcement-wrap-message = {$sender} Announcement: diff --git a/Resources/Locale/en-US/headset/headset-component.ftl b/Resources/Locale/en-US/headset/headset-component.ftl index f057757c20..9ff3ec06b3 100644 --- a/Resources/Locale/en-US/headset/headset-component.ftl +++ b/Resources/Locale/en-US/headset/headset-component.ftl @@ -1,8 +1,9 @@ # Chat window radio wrap (prefix and postfix) -chat-radio-message-wrap = {$channel} {$name} says, "{"{"}0{"}"}" +chat-radio-message-wrap = [color={$color}]{$channel} {$name} says, "{"{"}0{"}"}"[/color] examine-radio-frequency = It's set to broadcast over the {$frequency} frequency. examine-headset = A small screen on the headset displays the following available frequencies: +examine-headset-channel = [color={$color}]:{$key} for {$id} ({$freq})[/color] examine-headset-chat-prefix = Use {$prefix} for the currently tuned frequency. diff --git a/Resources/Prototypes/Entities/Clothing/Ears/headsets.yml b/Resources/Prototypes/Entities/Clothing/Ears/headsets.yml index 0ce99086c5..5da758a44e 100644 --- a/Resources/Prototypes/Entities/Clothing/Ears/headsets.yml +++ b/Resources/Prototypes/Entities/Clothing/Ears/headsets.yml @@ -19,6 +19,10 @@ name: cargo headset description: A headset used by the quartermaster and his slaves. components: + - type: Headset + channels: + - Common + - Supply - type: Sprite sprite: Clothing/Ears/Headsets/cargo.rsi @@ -28,6 +32,17 @@ name: centcomm headset description: A headset used by the upper echelons of Nanotrasen. components: + - type: Headset + channels: + - Common + - Command + - CentCom + - Engineering + - Medical + - Science + - Security + - Service + - Supply - type: Sprite sprite: Clothing/Ears/Headsets/centcom.rsi @@ -37,6 +52,16 @@ name: command headset description: A headset with a commanding channel. components: + - type: Headset + channels: + - Common + - Command + - Engineering + - Medical + - Science + - Security + - Service + - Supply - type: Sprite sprite: Clothing/Ears/Headsets/command.rsi @@ -46,6 +71,10 @@ name: engineering headset description: A headset for engineers to chat while the station burns around them. components: + - type: Headset + channels: + - Common + - Engineering - type: Sprite sprite: Clothing/Ears/Headsets/engineering.rsi @@ -55,6 +84,10 @@ name: medical headset description: A headset for the trained staff of the medbay. components: + - type: Headset + channels: + - Common + - Medical - type: Sprite sprite: Clothing/Ears/Headsets/medical.rsi @@ -64,6 +97,11 @@ name: medical research headset description: A headset that is a result of the mating between medical and science. components: + - type: Headset + channels: + - Common + - Medical + - Science - type: Sprite sprite: Clothing/Ears/Headsets/medicalscience.rsi @@ -91,6 +129,10 @@ name: science headset description: A sciency headset. Like usual. components: + - type: Headset + channels: + - Common + - Science - type: Sprite sprite: Clothing/Ears/Headsets/science.rsi @@ -100,6 +142,10 @@ name: security headset description: This is used by your elite security force. components: + - type: Headset + channels: + - Common + - Security - type: Sprite sprite: Clothing/Ears/Headsets/security.rsi @@ -109,5 +155,9 @@ name: service headset description: Headset used by the service staff, tasked with keeping the station full, happy and clean. components: + - type: Headset + channels: + - Common + - Service - type: Sprite sprite: Clothing/Ears/Headsets/service.rsi diff --git a/Resources/Prototypes/Entities/Clothing/Ears/headsets_alt.yml b/Resources/Prototypes/Entities/Clothing/Ears/headsets_alt.yml index 10087a9c39..f8eb04670d 100644 --- a/Resources/Prototypes/Entities/Clothing/Ears/headsets_alt.yml +++ b/Resources/Prototypes/Entities/Clothing/Ears/headsets_alt.yml @@ -17,6 +17,16 @@ id: ClothingHeadsetAltCommand name: command overear-headset components: + - type: Headset + channels: + - Common + - Command + - Engineering + - Medical + - Science + - Security + - Service + - Supply - type: Sprite sprite: Clothing/Ears/Headsets/command.rsi - type: Clothing @@ -27,6 +37,10 @@ id: ClothingHeadsetAltMedical name: medical overear-headset components: + - type: Headset + channels: + - Common + - Medical - type: Sprite sprite: Clothing/Ears/Headsets/medical.rsi - type: Clothing @@ -37,6 +51,10 @@ id: ClothingHeadsetAltSecurity name: security overear-headset components: + - type: Headset + channels: + - Common + - Security - type: Sprite sprite: Clothing/Ears/Headsets/security.rsi - type: Clothing @@ -48,6 +66,10 @@ name: syndicate overear-headset description: A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs. components: + - type: Headset + channels: + - Common + - Syndicate - type: Sprite sprite: Clothing/Ears/Headsets/syndicate.rsi - type: Clothing diff --git a/Resources/Prototypes/radio_channels.yml b/Resources/Prototypes/radio_channels.yml new file mode 100644 index 0000000000..8fd7bfb1d2 --- /dev/null +++ b/Resources/Prototypes/radio_channels.yml @@ -0,0 +1,66 @@ +- type: radioChannel + id: Common + name: "Common" + keycode: ";" + frequency: 1459 + +- type: radioChannel + id: CentCom + name: "CentCom" + keycode: 'y' + frequency: 1337 + +- type: radioChannel + id: Command + name: "Command" + keycode: 'c' + frequency: 1353 + color: "#334e6d" + +- type: radioChannel + id: Engineering + name: "Engineering" + keycode: 'e' + frequency: 1357 + color: "#efb341" + +- type: radioChannel + id: Medical + name: "Medical" + keycode: 'm' + frequency: 1355 + color: "#52b4e9" + +- type: radioChannel + id: Science + name: "Science" + keycode: 'n' + frequency: 1351 + color: "#d381c9" + +- type: radioChannel + id: Security + name: "Security" + keycode: 's' + frequency: 1359 + color: "#de3a3a" + +- type: radioChannel + id: Service + name: "Service" + keycode: 'v' + frequency: 1349 + color: "#9fed58" + +- type: radioChannel + id: Supply + name: "Supply" + keycode: 'u' + frequency: 1347 + color: "#a46106" + +- type: radioChannel + id: Syndicate + name: "Syndicate" + keycode: 't' + frequency: 1213