using Content.Server._NF.Radio; // Frontier using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; using Content.Server.Power.Components; using Content.Server.Radio.Components; using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Radio; using Content.Shared.Radio.Components; using Robust.Server.GameObjects; // Frontier using Content.Shared.Speech; using Content.Shared.Ghost; // Nuclear-14 using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Replays; using Robust.Shared.Utility; using Content.Shared._Horizon.Language; using Content.Server._Horizon.Language; namespace Content.Server.Radio.EntitySystems; /// /// This system handles intrinsic radios and the general process of converting radio messages into chat messages. /// 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 IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly LanguageSystem _language = default!; // set used to prevent radio feedback loops. private readonly HashSet _messages = new(); private EntityQuery _exemptQuery; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnIntrinsicReceive); SubscribeLocalEvent(OnIntrinsicSpeak); _exemptQuery = GetEntityQuery(); } private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntitySpokeEvent args) { if (args.Channel != null && component.Channels.Contains(args.Channel.ID)) { SendRadioMessage(uid, args.Message, args.Channel, uid, languageOverride: args.Language); args.Channel = null; // prevent duplicate messages from other listeners. } } //Nuclear-14 /// /// Gets the message frequency, if there is no such frequency, returns the standard channel frequency. /// public int GetFrequency(EntityUid source, RadioChannelPrototype channel) { if (TryComp(source, out var radioMicrophone)) return radioMicrophone.Frequency; return channel.Frequency; } private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args) { if (TryComp(uid, out ActorComponent? actor)) _netMan.ServerSendMessage(_language.CanUnderstand(uid, args.Language) ? args.ChatMsg : args.UnknownLanguageChatMsg, actor.PlayerSession.Channel); // Horizon languages } /// /// Send radio message to all active radio listeners /// public void SendRadioMessage(EntityUid messageSource, string message, ProtoId channel, EntityUid radioSource, int? frequency = null, bool escapeMarkup = true, LanguagePrototype? languageOverride = null) // Frontier: added frequency; Horizon - add language { SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, frequency: frequency, escapeMarkup: escapeMarkup, languageOverride: languageOverride); // Frontier: added frequency; Horizon - add language } /// /// 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, int? frequency = null, bool escapeMarkup = true, LanguagePrototype? languageOverride = null) // Nuclear-14: add frequency; Horizon - add language { // TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this. if (!_messages.Add(message)) return; // Horizon start var language = languageOverride ?? _language.GetCurrentLanguage(messageSource); if (language.LanguageType is not Generic gen) return; // Horizon end var evt = new TransformSpeakerNameEvent(messageSource, MetaData(messageSource).EntityName); RaiseLocalEvent(messageSource, evt); // Frontier: add name transform event var transformEv = new RadioTransformMessageEvent(channel, radioSource, evt.VoiceName, message, messageSource); RaiseLocalEvent(radioSource, ref transformEv); message = transformEv.Message; messageSource = transformEv.MessageSource; // End Frontier var name = transformEv.Name; // Frontier: evt.VoiceName verbStrings = speech.SpeechVerbStrings; bool verbsReplaced = false; foreach (var str in ILanguageType.SpeechSuffixes) { if (message.EndsWith(Loc.GetString(str)) && gen.SuffixSpeechVerbs.TryGetValue(str, out var strings) && strings.Count > 0) { verbStrings = strings; verbsReplaced = true; } } if (!verbsReplaced && gen.SuffixSpeechVerbs.TryGetValue("Default", out var defaultStrings) && defaultStrings.Count > 0) verbStrings = defaultStrings; /* var wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap", ("color", channel.Color), ("fontType", speech.FontId), ("fontSize", speech.FontSize), ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), ("channel", channelText), // Frontier: $"\\[{channel.LocalizedName}\\]"(); if (frequency == null) // Nuclear-14 frequency = GetFrequency(messageSource, channel); // Nuclear-14 while (canSend && radioQuery.MoveNext(out var receiver, out var radio, out var transform)) { if (!radio.ReceiveAllChannels) { if (!radio.Channels.Contains(channel.ID) || (TryComp(receiver, out var intercom) && !intercom.SupportedChannels.Contains(channel.ID))) continue; } if (!HasComp(receiver) && GetFrequency(receiver, channel) != frequency) // Nuclear-14 continue; // Nuclear-14 if (!channel.LongRange && transform.MapID != sourceMapId && !radio.GlobalReceive) continue; // don't need telecom server for long range channels or handheld radios and intercoms var needServer = !channel.LongRange && !sourceServerExempt; if (needServer && !hasActiveServer) continue; // check if message can be sent to specific receiver var attemptEv = new RadioReceiveAttemptEvent(channel, radioSource, receiver); RaiseLocalEvent(ref attemptEv); RaiseLocalEvent(receiver, ref attemptEv); if (attemptEv.Cancelled) continue; // send the message RaiseLocalEvent(receiver, ref ev); } 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(messageSource):user} on {channel.LocalizedName}: {message}"); _replay.RecordServerMessage(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; } }