using System.Linq; using Content.Shared.Chat; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Power; using Content.Shared.Power.EntitySystems; using Content.Shared.Radio.Components; using Content.Shared.Speech; using Content.Shared.Speech.Components; using Robust.Shared.Prototypes; namespace Content.Shared.Radio.EntitySystems; /// /// This system handles radio speakers and microphones (which together form a hand-held radio). /// public abstract partial class SharedRadioDeviceSystem : EntitySystem { [Dependency] private SharedAppearanceSystem _appearance = default!; [Dependency] private SharedPopupSystem _popup = default!; [Dependency] private SharedChatSystem _chat = default!; [Dependency] private SharedRadioSystem _radio = default!; [Dependency] private SharedPowerReceiverSystem _power = default!; [Dependency] private SharedInteractionSystem _interaction = default!; // Used to prevent a shitter from using a bunch of radios to spam chat. private readonly HashSet<(string, EntityUid, RadioChannelPrototype)> _recentlySent = []; /// public override void Update(float frameTime) { base.Update(frameTime); _recentlySent.Clear(); } #region Component Init [SubscribeLocalEvent] private void OnMicrophoneInit(Entity ent, ref ComponentInit args) { if (ent.Comp.Enabled) EnsureComp(ent).Range = ent.Comp.ListenRange; else RemCompDeferred(ent); } [SubscribeLocalEvent] private void OnSpeakerInit(Entity ent, ref ComponentInit args) { if (ent.Comp.Enabled) EnsureComp(ent).Channels.UnionWith(ent.Comp.Channels); else RemCompDeferred(ent); } #endregion #region Toggling [SubscribeLocalEvent] private void OnActivateMicrophone(Entity ent, ref ActivateInWorldEvent args) { if (!args.Complex) return; if (!ent.Comp.ToggleOnInteract) return; ToggleRadioMicrophone(ent.AsNullable(), args.User, args.Handled); args.Handled = true; } [SubscribeLocalEvent] private void OnActivateSpeaker(Entity ent, ref ActivateInWorldEvent args) { if (!args.Complex) return; if (!ent.Comp.ToggleOnInteract) return; ToggleRadioSpeaker(ent.AsNullable(), args.User, args.Handled); args.Handled = true; } [SubscribeLocalEvent] private void OnPowerChanged(Entity ent, ref PowerChangedEvent args) { if (args.Powered) return; SetMicrophoneEnabled(ent.AsNullable(), null, false, true); } /// /// Enables or disables a radio microphone. /// /// The entity with the microphone. /// The entity toggling the microphone, if any. /// Whether the microphone should be enabled. /// Whether to suppress the user-facing popup. public void SetMicrophoneEnabled( Entity ent, EntityUid? user, bool enabled, bool quiet = false ) { if (!Resolve(ent, ref ent.Comp, false)) return; if (ent.Comp.PowerRequired && !_power.IsPowered(ent.Owner)) return; ent.Comp.Enabled = enabled; Dirty(ent); if (!quiet && user != null) { var state = Loc.GetString(ent.Comp.Enabled ? "handheld-radio-component-on-state" : "handheld-radio-component-off-state"); var message = Loc.GetString("handheld-radio-component-on-use", ("radioState", state)); _popup.PopupEntity(message, user.Value, user.Value); } _appearance.SetData(ent, RadioDeviceVisuals.Broadcasting, ent.Comp.Enabled); if (ent.Comp.Enabled) EnsureComp(ent).Range = ent.Comp.ListenRange; else RemCompDeferred(ent); } #endregion #region Toggling /// /// Toggles a radio microphone. /// /// The entity with the microphone. /// The entity toggling the microphone. /// Whether to suppress the user-facing popup. public void ToggleRadioMicrophone( Entity ent, EntityUid user, bool quiet = false ) { if (!Resolve(ent, ref ent.Comp, false)) return; SetMicrophoneEnabled(ent, user, !ent.Comp.Enabled, quiet); } /// /// Toggles a radio speaker. /// /// The entity with the speaker. /// The entity toggling the speaker. /// Whether to suppress the user-facing popup. public void ToggleRadioSpeaker( Entity ent, EntityUid user, bool quiet = false ) { if (!Resolve(ent, ref ent.Comp, false)) return; SetSpeakerEnabled(ent, user, !ent.Comp.Enabled, quiet); } /// /// Enables or disables a radio speaker. /// /// The entity with the speaker. /// The entity toggling the speaker, if any. /// Whether the speaker should be enabled. /// Whether to suppress the user-facing popup. public void SetSpeakerEnabled( Entity ent, EntityUid? user, bool enabled, bool quiet = false ) { if (!Resolve(ent, ref ent.Comp, false)) return; ent.Comp.Enabled = enabled; Dirty(ent); if (!quiet && user != null) { var state = Loc.GetString(ent.Comp.Enabled ? "handheld-radio-component-on-state" : "handheld-radio-component-off-state"); var message = Loc.GetString("handheld-radio-component-on-use", ("radioState", state)); _popup.PopupEntity(message, user.Value, user.Value); } _appearance.SetData(ent, RadioDeviceVisuals.Speaker, ent.Comp.Enabled); if (ent.Comp.Enabled) EnsureComp(ent).Channels.UnionWith(ent.Comp.Channels); else RemCompDeferred(ent); } #endregion [SubscribeLocalEvent] private void OnExamine(Entity ent, ref ExaminedEvent args) { if (!args.IsInDetailsRange) return; var proto = ProtoMan.Index(ent.Comp.BroadcastChannel); using (args.PushGroup(nameof(RadioMicrophoneComponent))) { args.PushMarkup(Loc.GetString("radio-microphone-component-examine", ("color", proto.Color), ("channel", proto.LocalizedName), ("frequency", proto.Frequency))); } } [SubscribeLocalEvent] private void OnListen(Entity ent, ref ListenEvent args) { if (HasComp(args.Source)) return; // no feedback loops please. var channel = ProtoMan.Index(ent.Comp.BroadcastChannel); if (_recentlySent.Add((args.Message, args.Source, channel))) _radio.SendRadioMessage(args.Source, args.Message, channel, ent.Owner); } [SubscribeLocalEvent] private void OnAttemptListen(Entity ent, ref ListenAttemptEvent args) { if (ent.Comp.PowerRequired && !_power.IsPowered(ent.Owner) || ent.Comp.UnobstructedRequired && !_interaction.InRangeUnobstructed(args.Source, ent.Owner, 0)) { args.Cancel(); } } [SubscribeLocalEvent] private void OnReceiveRadio(Entity ent, ref RadioReceiveEvent args) { if (ent.Owner == args.RadioSource) return; var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource)); RaiseLocalEvent(args.MessageSource, nameEv); var name = Loc.GetString("speech-name-relay", ("speaker", Name(ent.Owner)), ("originalName", nameEv.VoiceName)); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios _chat.TrySendInGameICMessage(ent.Owner, args.Message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false); } [SubscribeLocalEvent] private void OnIntercomEncryptionChannelsChanged( Entity ent, ref EncryptionChannelsChangedEvent args ) { ent.Comp.SupportedChannels = args.Component.Channels.Select(p => new ProtoId(p)).ToList(); var channel = args.Component.DefaultChannel; if (ent.Comp.CurrentChannel != null && ent.Comp.SupportedChannels.Contains(ent.Comp.CurrentChannel.Value)) channel = ent.Comp.CurrentChannel; SetIntercomChannel(ent, channel); } [SubscribeLocalEvent] private void OnToggleIntercomMic(Entity ent, ref ToggleIntercomMicMessage args) { if (ent.Comp.RequiresPower && !_power.IsPowered(ent.Owner)) return; SetMicrophoneEnabled(ent.Owner, args.Actor, args.Enabled, true); ent.Comp.MicrophoneEnabled = args.Enabled; Dirty(ent); } [SubscribeLocalEvent] private void OnToggleIntercomSpeaker(Entity ent, ref ToggleIntercomSpeakerMessage args) { if (ent.Comp.RequiresPower && !_power.IsPowered(ent.Owner)) return; SetSpeakerEnabled(ent.Owner, args.Actor, args.Enabled, true); ent.Comp.SpeakerEnabled = args.Enabled; Dirty(ent); } [SubscribeLocalEvent] private void OnSelectIntercomChannel(Entity ent, ref SelectIntercomChannelMessage args) { if (ent.Comp.RequiresPower && !_power.IsPowered(ent.Owner)) return; if (!ProtoMan.HasIndex(args.Channel) || !ent.Comp.SupportedChannels.Contains(args.Channel)) return; SetIntercomChannel(ent, args.Channel); } private void SetIntercomChannel(Entity ent, ProtoId? channel) { ent.Comp.CurrentChannel = channel; if (TryComp(ent, out var mic)) { if (channel == null) { SetMicrophoneEnabled(ent.Owner, null, false); ent.Comp.MicrophoneEnabled = false; } else { mic.BroadcastChannel = channel.Value; Dirty(ent, mic); } } if (TryComp(ent, out var speaker)) { if (channel == null) { SetSpeakerEnabled(ent.Owner, null, false); ent.Comp.SpeakerEnabled = false; } else { speaker.Channels = [channel.Value]; Dirty(ent, speaker); } } Dirty(ent); } }