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