From 53681a8b3193c81b6b758bb449d14837838031b6 Mon Sep 17 00:00:00 2001 From: Slava0135 <40753025+Slava0135@users.noreply.github.com> Date: Fri, 24 Mar 2023 03:02:41 +0300 Subject: [PATCH] Add telecomms system (#14486) --- .../Anomaly/AnomalySystem.Generator.cs | 2 +- .../Radio/EntitySystems/HeadsetSystem.cs | 2 +- .../Radio/EntitySystems/RadioDeviceSystem.cs | 6 +- .../Radio/EntitySystems/RadioSystem.cs | 80 +++++++++++++------ .../Radio/EntitySystems/TelecomSystem.cs | 5 -- Content.Server/Radio/RadioReceiveEvent.cs | 29 ++++--- Content.Server/Salvage/SalvageSystem.cs | 2 +- .../StationEvents/Events/SolarFlare.cs | 8 +- .../Components/TelecomServerComponent.cs | 8 +- Content.Shared/Radio/RadioChannelPrototype.cs | 48 ++++++----- Resources/Prototypes/radio_channels.yml | 2 + 11 files changed, 121 insertions(+), 71 deletions(-) delete mode 100644 Content.Server/Radio/EntitySystems/TelecomSystem.cs diff --git a/Content.Server/Anomaly/AnomalySystem.Generator.cs b/Content.Server/Anomaly/AnomalySystem.Generator.cs index ba7c7b981c..924c3a2a42 100644 --- a/Content.Server/Anomaly/AnomalySystem.Generator.cs +++ b/Content.Server/Anomaly/AnomalySystem.Generator.cs @@ -157,7 +157,7 @@ public sealed partial class AnomalySystem Audio.PlayPvs(component.GeneratingFinishedSound, uid); var message = Loc.GetString("anomaly-generator-announcement"); - _radio.SendRadioMessage(uid, message, _prototype.Index(component.ScienceChannel)); + _radio.SendRadioMessage(uid, message, _prototype.Index(component.ScienceChannel), uid); } private void UpdateGenerator() diff --git a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs index 87ec6448ae..4f7ab752c8 100644 --- a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs +++ b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs @@ -93,7 +93,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem } } - private void OnHeadsetReceive(EntityUid uid, HeadsetComponent component, RadioReceiveEvent args) + private void OnHeadsetReceive(EntityUid uid, HeadsetComponent component, ref RadioReceiveEvent args) { if (TryComp(Transform(uid).ParentUid, out ActorComponent? actor)) _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.ConnectedClient); diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index fc2d89ddbb..c8de625025 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -206,10 +206,10 @@ public sealed class RadioDeviceSystem : EntitySystem } } - private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, RadioReceiveEvent args) + private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref RadioReceiveEvent args) { - var nameEv = new TransformSpeakerNameEvent(args.Source, Name(args.Source)); - RaiseLocalEvent(args.Source, nameEv); + var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource)); + RaiseLocalEvent(args.MessageSource, nameEv); var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)), ("originalName", nameEv.Name)); diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index 6580afc17f..15e439465e 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -11,6 +11,9 @@ using Robust.Shared.Network; using Robust.Shared.Replays; using Robust.Shared.Utility; using Content.Shared.Popups; +using Robust.Shared.Map; +using Content.Shared.Radio.Components; +using Content.Server.Power.Components; namespace Content.Server.Radio.EntitySystems; @@ -22,7 +25,7 @@ public sealed class RadioSystem : EntitySystem [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly IReplayRecordingManager _replay = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly PopupSystem _popup = default!; // set used to prevent radio feedback loops. private readonly HashSet _messages = new(); @@ -38,26 +41,31 @@ public sealed class RadioSystem : EntitySystem { if (args.Channel != null && component.Channels.Contains(args.Channel.ID)) { - SendRadioMessage(uid, args.Message, args.Channel); + SendRadioMessage(uid, args.Message, args.Channel, uid); args.Channel = null; // prevent duplicate messages from other listeners. } } - private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, RadioReceiveEvent args) + private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args) { if (TryComp(uid, out ActorComponent? actor)) _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.ConnectedClient); } - public void SendRadioMessage(EntityUid source, string message, RadioChannelPrototype channel, EntityUid? radioSource = null) + /// + /// Send radio message to all active radio listeners + /// + /// Entity that spoke the message + /// Entity that picked up the message and will send it, e.g. headset + public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource) { // TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this. if (!_messages.Add(message)) return; - var name = TryComp(source, out VoiceMaskComponent? mask) && mask.Enabled + var name = TryComp(messageSource, out VoiceMaskComponent? mask) && mask.Enabled ? mask.VoiceName - : MetaData(source).EntityName; + : MetaData(messageSource).EntityName; name = FormattedMessage.EscapeText(name); @@ -68,37 +76,63 @@ public sealed class RadioSystem : EntitySystem Loc.GetString("chat-radio-message-wrap", ("color", channel.Color), ("channel", $"\\[{channel.LocalizedName}\\]"), ("name", name), ("message", FormattedMessage.EscapeText(message))), EntityUid.Invalid); var chatMsg = new MsgChatMessage { Message = chat }; + var ev = new RadioReceiveEvent(message, messageSource, channel, chatMsg); - var ev = new RadioReceiveEvent(message, source, channel, chatMsg, radioSource); - var attemptEv = new RadioReceiveAttemptEvent(message, source, channel, radioSource); + var sourceMapId = Transform(radioSource).MapID; + var hasActiveServer = HasActiveServer(sourceMapId, channel.ID); + var hasMicro = HasComp(radioSource); + + var speakerQuery = GetEntityQuery(); + var radioQuery = AllEntityQuery(); var sentAtLeastOnce = false; - - foreach (var radio in EntityQuery()) + while (radioQuery.MoveNext(out var receiver, out var radio, out var transform)) { - var ent = radio.Owner; - // TODO map/station/range checks? - if (!radio.Channels.Contains(channel.ID)) continue; - RaiseLocalEvent(ent, attemptEv); - if (attemptEv.Cancelled) - { - attemptEv.Uncancel(); + if (!channel.LongRange && transform.MapID != sourceMapId) continue; - } + + // don't need telecom server for long range channels or handheld radios and intercoms + var needServer = !channel.LongRange && (!hasMicro || !speakerQuery.HasComponent(receiver)); + if (needServer && !hasActiveServer) + continue; + + // check if message can be sent to specific receiver + var attemptEv = new RadioReceiveAttemptEvent(channel, radioSource, receiver); + RaiseLocalEvent(ref attemptEv); + if (attemptEv.Cancelled) + continue; + + // send the message + RaiseLocalEvent(receiver, ref ev); sentAtLeastOnce = true; - RaiseLocalEvent(ent, ev); } if (!sentAtLeastOnce) - _popupSystem.PopupEntity(Loc.GetString("failed-to-send-message"), source, source, PopupType.MediumCaution); + _popup.PopupEntity(Loc.GetString("failed-to-send-message"), messageSource, messageSource, PopupType.MediumCaution); - if (name != Name(source)) - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(source):user} as {name} on {channel.LocalizedName}: {message}"); + if (name != Name(messageSource)) + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(messageSource):user} as {name} on {channel.LocalizedName}: {message}"); else - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(source):user} on {channel.LocalizedName}: {message}"); + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(messageSource):user} on {channel.LocalizedName}: {message}"); _replay.QueueReplayMessage(chat); _messages.Remove(message); } + + /// + private bool HasActiveServer(MapId mapId, string channelId) + { + var servers = EntityQuery(); + foreach (var (_, keys, power, transform) in servers) + { + if (transform.MapID == mapId && + power.Powered && + keys.Channels.Contains(channelId)) + { + return true; + } + } + return false; + } } diff --git a/Content.Server/Radio/EntitySystems/TelecomSystem.cs b/Content.Server/Radio/EntitySystems/TelecomSystem.cs deleted file mode 100644 index b4d8897a73..0000000000 --- a/Content.Server/Radio/EntitySystems/TelecomSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Content.Server.Radio.EntitySystems; - -public sealed class TelecomSystem : EntitySystem -{ -} diff --git a/Content.Server/Radio/RadioReceiveEvent.cs b/Content.Server/Radio/RadioReceiveEvent.cs index f8d8240b8d..abcee61025 100644 --- a/Content.Server/Radio/RadioReceiveEvent.cs +++ b/Content.Server/Radio/RadioReceiveEvent.cs @@ -3,36 +3,39 @@ using Content.Shared.Radio; namespace Content.Server.Radio; -public sealed class RadioReceiveEvent : EntityEventArgs +[ByRefEvent] +public struct RadioReceiveEvent { public readonly string Message; - public readonly EntityUid Source; + public readonly EntityUid MessageSource; public readonly RadioChannelPrototype Channel; public readonly MsgChatMessage ChatMsg; - public readonly EntityUid? RadioSource; - public RadioReceiveEvent(string message, EntityUid source, RadioChannelPrototype channel, MsgChatMessage chatMsg, EntityUid? radioSource) + public RadioReceiveEvent(string message, EntityUid messageSource, RadioChannelPrototype channel, MsgChatMessage chatMsg) { Message = message; - Source = source; + MessageSource = messageSource; Channel = channel; ChatMsg = chatMsg; - RadioSource = radioSource; } } -public sealed class RadioReceiveAttemptEvent : CancellableEntityEventArgs +/// +/// Use this event to cancel sending messages by doing various checks (e.g. range) +/// +[ByRefEvent] +public struct RadioReceiveAttemptEvent { - public readonly string Message; - public readonly EntityUid Source; public readonly RadioChannelPrototype Channel; - public readonly EntityUid? RadioSource; + public readonly EntityUid RadioSource; + public readonly EntityUid RadioReceiver; - public RadioReceiveAttemptEvent(string message, EntityUid source, RadioChannelPrototype channel, EntityUid? radioSource) + public bool Cancelled = false; + + public RadioReceiveAttemptEvent(RadioChannelPrototype channel, EntityUid radioSource, EntityUid radioReceiver) { - Message = message; - Source = source; Channel = channel; RadioSource = radioSource; + RadioReceiver = radioReceiver; } } diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs index b0ce24ddba..8fe166f3c7 100644 --- a/Content.Server/Salvage/SalvageSystem.cs +++ b/Content.Server/Salvage/SalvageSystem.cs @@ -376,7 +376,7 @@ namespace Content.Server.Salvage var message = args.Length == 0 ? Loc.GetString(messageKey) : Loc.GetString(messageKey, args); var channel = _prototypeManager.Index(channelName); - _radioSystem.SendRadioMessage(source, message, channel); + _radioSystem.SendRadioMessage(source, message, channel, source); } private void Transition(SalvageMagnetComponent magnet, TimeSpan currentTime) diff --git a/Content.Server/StationEvents/Events/SolarFlare.cs b/Content.Server/StationEvents/Events/SolarFlare.cs index 9c1b3114db..cf3a2a403b 100644 --- a/Content.Server/StationEvents/Events/SolarFlare.cs +++ b/Content.Server/StationEvents/Events/SolarFlare.cs @@ -23,7 +23,7 @@ public sealed class SolarFlare : StationEventSystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnRadioSendAttempt); + SubscribeLocalEvent(OnRadioSendAttempt); } public override void Added() @@ -69,10 +69,10 @@ public sealed class SolarFlare : StationEventSystem } } - private void OnRadioSendAttempt(EntityUid uid, ActiveRadioComponent component, RadioReceiveAttemptEvent args) + private void OnRadioSendAttempt(ref RadioReceiveAttemptEvent args) { if (RuleStarted && _event.AffectedChannels.Contains(args.Channel.ID)) - if (!_event.OnlyJamHeadsets || (HasComp(uid) || HasComp(args.RadioSource))) - args.Cancel(); + if (!_event.OnlyJamHeadsets || (HasComp(args.RadioReceiver) || HasComp(args.RadioSource))) + args.Cancelled = true; } } diff --git a/Content.Shared/Radio/Components/TelecomServerComponent.cs b/Content.Shared/Radio/Components/TelecomServerComponent.cs index 2aa003a1e7..d87bc2be1a 100644 --- a/Content.Shared/Radio/Components/TelecomServerComponent.cs +++ b/Content.Shared/Radio/Components/TelecomServerComponent.cs @@ -1,6 +1,12 @@ namespace Content.Shared.Radio.Components; +/// +/// Entities with are needed to transmit messages using headsets. +/// They also need to be powered by +/// have and filled with encryption keys +/// of channels in order for them to work on the same map as server. +/// [RegisterComponent] public sealed class TelecomServerComponent : Component { -} \ No newline at end of file +} diff --git a/Content.Shared/Radio/RadioChannelPrototype.cs b/Content.Shared/Radio/RadioChannelPrototype.cs index c72cb0c0d9..b8b862f61f 100644 --- a/Content.Shared/Radio/RadioChannelPrototype.cs +++ b/Content.Shared/Radio/RadioChannelPrototype.cs @@ -1,28 +1,38 @@ using Robust.Shared.Prototypes; -namespace Content.Shared.Radio +namespace Content.Shared.Radio; + +[Prototype("radioChannel")] +public sealed class RadioChannelPrototype : IPrototype { - [Prototype("radioChannel")] - public sealed class RadioChannelPrototype : IPrototype - { - /// - /// Human-readable name for the channel. - /// - [DataField("name")] public string Name { get; private set; } = string.Empty; + /// + /// Human-readable name for the channel. + /// + [DataField("name")] + public string Name { get; private set; } = string.Empty; - [ViewVariables(VVAccess.ReadOnly)] public string LocalizedName => Loc.GetString(Name); + [ViewVariables(VVAccess.ReadOnly)] + public string LocalizedName => Loc.GetString(Name); - /// - /// Single-character prefix to determine what channel a message should be sent to. - /// - [DataField("keycode")] public char KeyCode { get; private set; } = '\0'; + /// + /// Single-character prefix to determine what channel a message should be sent to. + /// + [DataField("keycode")] + public char KeyCode { get; private set; } = '\0'; - [DataField("frequency")] public int Frequency { get; private set; } = 0; + [DataField("frequency")] + public int Frequency { get; private set; } = 0; - [DataField("color")] public Color Color { get; private set; } = Color.Lime; + [DataField("color")] + public Color Color { get; private set; } = Color.Lime; - [ViewVariables] - [IdDataField] - public string ID { get; } = default!; - } + [IdDataField, ViewVariables] + public string ID { get; } = default!; + + /// + /// If channel is long range it doesn't require telecommunication server + /// and messages can be sent across different stations + /// + [DataField("longRange"), ViewVariables] + public bool LongRange = false; } diff --git a/Resources/Prototypes/radio_channels.yml b/Resources/Prototypes/radio_channels.yml index 734568fc90..c72f250a26 100644 --- a/Resources/Prototypes/radio_channels.yml +++ b/Resources/Prototypes/radio_channels.yml @@ -11,6 +11,7 @@ keycode: 'y' frequency: 1337 color: "#2681a5" + longRange: true - type: radioChannel id: Command @@ -67,3 +68,4 @@ keycode: 't' frequency: 1213 color: "#8f4a4b" + longRange: true