2022-12-24 14:50:34 -06:00
using Content.Server.Administration.Logs ;
2022-11-15 17:09:27 +13:00
using Content.Server.Chat.Systems ;
2021-06-09 22:19:39 +02:00
using Content.Server.Radio.Components ;
2022-11-15 17:09:27 +13:00
using Content.Server.VoiceMask ;
2023-02-11 23:24:29 +03:00
using Content.Server.Popups ;
2022-11-15 17:09:27 +13:00
using Content.Shared.Chat ;
2022-12-24 14:50:34 -06:00
using Content.Shared.Database ;
2022-06-23 20:11:03 +10:00
using Content.Shared.Radio ;
2022-11-15 17:09:27 +13:00
using Robust.Server.GameObjects ;
using Robust.Shared.Network ;
2022-11-23 00:52:19 +13:00
using Robust.Shared.Replays ;
2022-11-15 17:09:27 +13:00
using Robust.Shared.Utility ;
2023-02-11 23:24:29 +03:00
using Content.Shared.Popups ;
2023-03-24 03:02:41 +03:00
using Robust.Shared.Map ;
using Content.Shared.Radio.Components ;
using Content.Server.Power.Components ;
2023-08-15 13:03:05 -07:00
using Robust.Shared.Random ;
2020-07-28 16:13:39 -06:00
2022-11-15 17:09:27 +13:00
namespace Content.Server.Radio.EntitySystems ;
/// <summary>
2022-11-23 00:52:19 +13:00
/// This system handles intrinsic radios and the general process of converting radio messages into chat messages.
2022-11-15 17:09:27 +13:00
/// </summary>
public sealed class RadioSystem : EntitySystem
2020-07-28 16:13:39 -06:00
{
2022-11-15 17:09:27 +13:00
[Dependency] private readonly INetManager _netMan = default ! ;
2022-11-23 00:52:19 +13:00
[Dependency] private readonly IReplayRecordingManager _replay = default ! ;
2022-12-24 14:50:34 -06:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2023-08-15 13:03:05 -07:00
[Dependency] private readonly IRobustRandom _random = default ! ;
2023-03-24 03:02:41 +03:00
[Dependency] private readonly PopupSystem _popup = default ! ;
2023-08-15 13:03:05 -07:00
[Dependency] private readonly ChatSystem _chat = default ! ;
2022-11-15 17:09:27 +13:00
// set used to prevent radio feedback loops.
private readonly HashSet < string > _messages = new ( ) ;
public override void Initialize ( )
2020-07-28 16:13:39 -06:00
{
2022-11-15 17:09:27 +13:00
base . Initialize ( ) ;
SubscribeLocalEvent < IntrinsicRadioReceiverComponent , RadioReceiveEvent > ( OnIntrinsicReceive ) ;
SubscribeLocalEvent < IntrinsicRadioTransmitterComponent , EntitySpokeEvent > ( OnIntrinsicSpeak ) ;
}
2020-10-07 14:02:12 +02:00
2022-11-15 17:09:27 +13:00
private void OnIntrinsicSpeak ( EntityUid uid , IntrinsicRadioTransmitterComponent component , EntitySpokeEvent args )
{
if ( args . Channel ! = null & & component . Channels . Contains ( args . Channel . ID ) )
2022-04-08 17:17:25 -04:00
{
2023-03-24 03:02:41 +03:00
SendRadioMessage ( uid , args . Message , args . Channel , uid ) ;
2022-11-15 17:09:27 +13:00
args . Channel = null ; // prevent duplicate messages from other listeners.
2022-07-14 22:29:29 +12:00
}
2022-11-15 17:09:27 +13:00
}
2022-07-14 22:29:29 +12:00
2023-03-24 03:02:41 +03:00
private void OnIntrinsicReceive ( EntityUid uid , IntrinsicRadioReceiverComponent component , ref RadioReceiveEvent args )
2022-11-15 17:09:27 +13:00
{
if ( TryComp ( uid , out ActorComponent ? actor ) )
_netMan . ServerSendMessage ( args . ChatMsg , actor . PlayerSession . ConnectedClient ) ;
}
2022-07-14 22:29:29 +12:00
2023-03-24 03:02:41 +03:00
/// <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 )
2022-11-15 17:09:27 +13:00
{
// TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
if ( ! _messages . Add ( message ) )
return ;
2023-03-24 03:02:41 +03:00
var name = TryComp ( messageSource , out VoiceMaskComponent ? mask ) & & mask . Enabled
2023-02-05 15:38:14 +00:00
? mask . VoiceName
2023-03-24 03:02:41 +03:00
: MetaData ( messageSource ) . EntityName ;
2022-04-08 17:17:25 -04:00
2022-11-15 17:09:27 +13:00
name = FormattedMessage . EscapeText ( name ) ;
2023-08-15 13:03:05 -07:00
var speech = _chat . GetSpeechVerb ( messageSource , 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" , FormattedMessage . EscapeText ( message ) ) ) ;
2022-11-15 17:09:27 +13:00
// most radios are relayed to chat, so lets parse the chat message beforehand
2022-11-23 00:52:19 +13:00
var chat = new ChatMessage (
ChatChannel . Radio ,
message ,
2023-08-15 13:03:05 -07:00
wrappedMessage ,
2022-11-23 00:52:19 +13:00
EntityUid . Invalid ) ;
var chatMsg = new MsgChatMessage { Message = chat } ;
2023-03-24 03:02:41 +03:00
var ev = new RadioReceiveEvent ( message , messageSource , channel , chatMsg ) ;
2022-04-08 17:17:25 -04:00
2023-04-14 22:50:19 +03:00
var sendAttemptEv = new RadioSendAttemptEvent ( channel , radioSource ) ;
RaiseLocalEvent ( ref sendAttemptEv ) ;
2023-05-07 01:26:04 +10:00
RaiseLocalEvent ( radioSource , ref sendAttemptEv ) ;
2023-04-14 22:50:19 +03:00
var canSend = ! sendAttemptEv . Cancelled ;
2023-03-24 03:02:41 +03:00
var sourceMapId = Transform ( radioSource ) . MapID ;
var hasActiveServer = HasActiveServer ( sourceMapId , channel . ID ) ;
var hasMicro = HasComp < RadioMicrophoneComponent > ( radioSource ) ;
2022-11-15 17:09:27 +13:00
2023-03-24 03:02:41 +03:00
var speakerQuery = GetEntityQuery < RadioSpeakerComponent > ( ) ;
2023-04-23 11:29:08 +03:00
var radioQuery = EntityQueryEnumerator < ActiveRadioComponent , TransformComponent > ( ) ;
2023-04-14 22:50:19 +03:00
while ( canSend & & radioQuery . MoveNext ( out var receiver , out var radio , out var transform ) )
2020-10-07 14:02:12 +02:00
{
2023-07-11 19:58:18 -04:00
if ( ! radio . Channels . Contains ( channel . ID ) | | ( TryComp < IntercomComponent > ( receiver , out var intercom ) & & ! intercom . SupportedChannels . Contains ( channel . ID ) ) )
2022-11-15 17:09:27 +13:00
continue ;
2020-07-28 16:13:39 -06:00
2023-04-02 19:56:07 -04:00
if ( ! channel . LongRange & & transform . MapID ! = sourceMapId & & ! radio . GlobalReceive )
2023-03-24 03:02:41 +03:00
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 ;
2023-04-02 19:56:07 -04:00
2023-03-24 03:02:41 +03:00
// check if message can be sent to specific receiver
var attemptEv = new RadioReceiveAttemptEvent ( channel , radioSource , receiver ) ;
RaiseLocalEvent ( ref attemptEv ) ;
2023-05-07 01:26:04 +10:00
RaiseLocalEvent ( receiver , ref attemptEv ) ;
2022-11-15 17:09:27 +13:00
if ( attemptEv . Cancelled )
continue ;
2023-03-24 03:02:41 +03:00
// send the message
RaiseLocalEvent ( receiver , ref ev ) ;
2020-07-28 16:13:39 -06:00
}
2022-11-15 17:09:27 +13:00
2023-03-24 03:02:41 +03:00
if ( name ! = Name ( messageSource ) )
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Radio message from {ToPrettyString(messageSource):user} as {name} on {channel.LocalizedName}: {message}" ) ;
2023-02-11 13:26:44 -06:00
else
2023-03-24 03:02:41 +03:00
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Radio message from {ToPrettyString(messageSource):user} on {channel.LocalizedName}: {message}" ) ;
2022-12-24 14:50:34 -06:00
2023-06-19 05:23:31 +12:00
_replay . RecordServerMessage ( chat ) ;
2022-11-15 17:09:27 +13:00
_messages . Remove ( message ) ;
2020-07-28 16:13:39 -06:00
}
2023-03-24 03:02:41 +03:00
/// <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 ;
}
2020-07-28 16:13:39 -06:00
}