| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- 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 Content.Shared.Speech;
- 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;
- namespace Content.Server.Radio.EntitySystems;
- /// <summary>
- /// This system handles intrinsic radios and the general process of converting radio messages into chat messages.
- /// </summary>
- 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!;
- // set used to prevent radio feedback loops.
- private readonly HashSet<string> _messages = new();
- private EntityQuery<TelecomExemptComponent> _exemptQuery;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
- SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
- _exemptQuery = GetEntityQuery<TelecomExemptComponent>();
- }
- 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);
- args.Channel = null; // prevent duplicate messages from other listeners.
- }
- }
- private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args)
- {
- if (TryComp(uid, out ActorComponent? actor))
- _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel);
- }
- /// <summary>
- /// Send radio message to all active radio listeners
- /// </summary>
- public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true)
- {
- SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup);
- }
- /// <summary>
- /// Send radio message to all active radio listeners
- /// </summary>
- /// <param name="messageSource">Entity that spoke the message</param>
- /// <param name="radioSource">Entity that picked up the message and will send it, e.g. headset</param>
- public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true)
- {
- // TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
- if (!_messages.Add(message))
- return;
- var evt = new TransformSpeakerNameEvent(messageSource, MetaData(messageSource).EntityName);
- RaiseLocalEvent(messageSource, evt);
- var name = evt.VoiceName;
- name = FormattedMessage.EscapeText(name);
- SpeechVerbPrototype speech;
- if (evt.SpeechVerb != null && _prototype.TryIndex(evt.SpeechVerb, out var evntProto))
- speech = evntProto;
- else
- speech = _chat.GetSpeechVerb(messageSource, message);
- var content = escapeMarkup
- ? FormattedMessage.EscapeText(message)
- : message;
- 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", $"\\[{channel.LocalizedName}\\]"),
- ("name", name),
- ("message", content));
- // most radios are relayed to chat, so lets parse the chat message beforehand
- var chat = new ChatMessage(
- ChatChannel.Radio,
- message,
- wrappedMessage,
- NetEntity.Invalid,
- null);
- var chatMsg = new MsgChatMessage { Message = chat };
- var ev = new RadioReceiveEvent(message, messageSource, channel, radioSource, chatMsg);
- var sendAttemptEv = new RadioSendAttemptEvent(channel, radioSource);
- RaiseLocalEvent(ref sendAttemptEv);
- RaiseLocalEvent(radioSource, ref sendAttemptEv);
- var canSend = !sendAttemptEv.Cancelled;
- var sourceMapId = Transform(radioSource).MapID;
- var hasActiveServer = HasActiveServer(sourceMapId, channel.ID);
- var sourceServerExempt = _exemptQuery.HasComp(radioSource);
- var radioQuery = EntityQueryEnumerator<ActiveRadioComponent, TransformComponent>();
- while (canSend && radioQuery.MoveNext(out var receiver, out var radio, out var transform))
- {
- if (!radio.ReceiveAllChannels)
- {
- if (!radio.Channels.Contains(channel.ID) || (TryComp<IntercomComponent>(receiver, out var intercom) &&
- !intercom.SupportedChannels.Contains(channel.ID)))
- continue;
- }
- 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);
- }
- /// <inheritdoc cref="TelecomServerComponent"/>
- private bool HasActiveServer(MapId mapId, string channelId)
- {
- var servers = EntityQuery<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent, TransformComponent>();
- foreach (var (_, keys, power, transform) in servers)
- {
- if (transform.MapID == mapId &&
- power.Powered &&
- keys.Channels.Contains(channelId))
- {
- return true;
- }
- }
- return false;
- }
- }
|