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