RadioSystem.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Chat.Systems;
  3. using Content.Server.Power.Components;
  4. using Content.Server.Radio.Components;
  5. using Content.Shared.Chat;
  6. using Content.Shared.Database;
  7. using Content.Shared.Radio;
  8. using Content.Shared.Radio.Components;
  9. using Content.Shared.Speech;
  10. using Robust.Shared.Map;
  11. using Robust.Shared.Network;
  12. using Robust.Shared.Player;
  13. using Robust.Shared.Prototypes;
  14. using Robust.Shared.Random;
  15. using Robust.Shared.Replays;
  16. using Robust.Shared.Utility;
  17. namespace Content.Server.Radio.EntitySystems;
  18. /// <summary>
  19. /// This system handles intrinsic radios and the general process of converting radio messages into chat messages.
  20. /// </summary>
  21. public sealed class RadioSystem : EntitySystem
  22. {
  23. [Dependency] private readonly INetManager _netMan = default!;
  24. [Dependency] private readonly IReplayRecordingManager _replay = default!;
  25. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  26. [Dependency] private readonly IPrototypeManager _prototype = default!;
  27. [Dependency] private readonly IRobustRandom _random = default!;
  28. [Dependency] private readonly ChatSystem _chat = default!;
  29. // set used to prevent radio feedback loops.
  30. private readonly HashSet<string> _messages = new();
  31. private EntityQuery<TelecomExemptComponent> _exemptQuery;
  32. public override void Initialize()
  33. {
  34. base.Initialize();
  35. SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
  36. SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
  37. _exemptQuery = GetEntityQuery<TelecomExemptComponent>();
  38. }
  39. private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntitySpokeEvent args)
  40. {
  41. if (args.Channel != null && component.Channels.Contains(args.Channel.ID))
  42. {
  43. SendRadioMessage(uid, args.Message, args.Channel, uid);
  44. args.Channel = null; // prevent duplicate messages from other listeners.
  45. }
  46. }
  47. private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args)
  48. {
  49. if (TryComp(uid, out ActorComponent? actor))
  50. _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel);
  51. }
  52. /// <summary>
  53. /// Send radio message to all active radio listeners
  54. /// </summary>
  55. public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true)
  56. {
  57. SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup);
  58. }
  59. /// <summary>
  60. /// Send radio message to all active radio listeners
  61. /// </summary>
  62. /// <param name="messageSource">Entity that spoke the message</param>
  63. /// <param name="radioSource">Entity that picked up the message and will send it, e.g. headset</param>
  64. public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true)
  65. {
  66. // TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
  67. if (!_messages.Add(message))
  68. return;
  69. var evt = new TransformSpeakerNameEvent(messageSource, MetaData(messageSource).EntityName);
  70. RaiseLocalEvent(messageSource, evt);
  71. var name = evt.VoiceName;
  72. name = FormattedMessage.EscapeText(name);
  73. SpeechVerbPrototype speech;
  74. if (evt.SpeechVerb != null && _prototype.TryIndex(evt.SpeechVerb, out var evntProto))
  75. speech = evntProto;
  76. else
  77. speech = _chat.GetSpeechVerb(messageSource, message);
  78. var content = escapeMarkup
  79. ? FormattedMessage.EscapeText(message)
  80. : message;
  81. var wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
  82. ("color", channel.Color),
  83. ("fontType", speech.FontId),
  84. ("fontSize", speech.FontSize),
  85. ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
  86. ("channel", $"\\[{channel.LocalizedName}\\]"),
  87. ("name", name),
  88. ("message", content));
  89. // most radios are relayed to chat, so lets parse the chat message beforehand
  90. var chat = new ChatMessage(
  91. ChatChannel.Radio,
  92. message,
  93. wrappedMessage,
  94. NetEntity.Invalid,
  95. null);
  96. var chatMsg = new MsgChatMessage { Message = chat };
  97. var ev = new RadioReceiveEvent(message, messageSource, channel, radioSource, chatMsg);
  98. var sendAttemptEv = new RadioSendAttemptEvent(channel, radioSource);
  99. RaiseLocalEvent(ref sendAttemptEv);
  100. RaiseLocalEvent(radioSource, ref sendAttemptEv);
  101. var canSend = !sendAttemptEv.Cancelled;
  102. var sourceMapId = Transform(radioSource).MapID;
  103. var hasActiveServer = HasActiveServer(sourceMapId, channel.ID);
  104. var sourceServerExempt = _exemptQuery.HasComp(radioSource);
  105. var radioQuery = EntityQueryEnumerator<ActiveRadioComponent, TransformComponent>();
  106. while (canSend && radioQuery.MoveNext(out var receiver, out var radio, out var transform))
  107. {
  108. if (!radio.ReceiveAllChannels)
  109. {
  110. if (!radio.Channels.Contains(channel.ID) || (TryComp<IntercomComponent>(receiver, out var intercom) &&
  111. !intercom.SupportedChannels.Contains(channel.ID)))
  112. continue;
  113. }
  114. if (!channel.LongRange && transform.MapID != sourceMapId && !radio.GlobalReceive)
  115. continue;
  116. // don't need telecom server for long range channels or handheld radios and intercoms
  117. var needServer = !channel.LongRange && !sourceServerExempt;
  118. if (needServer && !hasActiveServer)
  119. continue;
  120. // check if message can be sent to specific receiver
  121. var attemptEv = new RadioReceiveAttemptEvent(channel, radioSource, receiver);
  122. RaiseLocalEvent(ref attemptEv);
  123. RaiseLocalEvent(receiver, ref attemptEv);
  124. if (attemptEv.Cancelled)
  125. continue;
  126. // send the message
  127. RaiseLocalEvent(receiver, ref ev);
  128. }
  129. if (name != Name(messageSource))
  130. _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(messageSource):user} as {name} on {channel.LocalizedName}: {message}");
  131. else
  132. _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(messageSource):user} on {channel.LocalizedName}: {message}");
  133. _replay.RecordServerMessage(chat);
  134. _messages.Remove(message);
  135. }
  136. /// <inheritdoc cref="TelecomServerComponent"/>
  137. private bool HasActiveServer(MapId mapId, string channelId)
  138. {
  139. var servers = EntityQuery<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent, TransformComponent>();
  140. foreach (var (_, keys, power, transform) in servers)
  141. {
  142. if (transform.MapID == mapId &&
  143. power.Powered &&
  144. keys.Channels.Contains(channelId))
  145. {
  146. return true;
  147. }
  148. }
  149. return false;
  150. }
  151. }