using Content.Server.Chat.Systems; using Content.Server.Interaction; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Radio.Components; using Content.Server.Speech; using Content.Server.Speech.Components; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Radio; using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Prototypes; namespace Content.Server.Radio.EntitySystems; /// /// This system handles radio speakers and microphones (which together form a hand-held radio). /// public sealed class RadioDeviceSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly RadioSystem _radio = default!; [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; // Used to prevent a shitter from using a bunch of radios to spam chat. private HashSet<(string, EntityUid)> _recentlySent = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMicrophoneInit); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnActivateMicrophone); SubscribeLocalEvent(OnListen); SubscribeLocalEvent(OnAttemptListen); SubscribeLocalEvent>(OnGetVerbs); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnSpeakerInit); SubscribeLocalEvent(OnActivateSpeaker); SubscribeLocalEvent(OnReceiveRadio); } public override void Update(float frameTime) { base.Update(frameTime); _recentlySent.Clear(); } #region Component Init private void OnMicrophoneInit(EntityUid uid, RadioMicrophoneComponent component, ComponentInit args) { if (component.Enabled) EnsureComp(uid).Range = component.ListenRange; else RemCompDeferred(uid); } private void OnSpeakerInit(EntityUid uid, RadioSpeakerComponent component, ComponentInit args) { if (component.Enabled) EnsureComp(uid).Channels.UnionWith(component.Channels); else RemCompDeferred(uid); } #endregion #region Toggling private void OnActivateMicrophone(EntityUid uid, RadioMicrophoneComponent component, ActivateInWorldEvent args) { ToggleRadioMicrophone(uid, args.User, args.Handled, component); args.Handled = true; } private void OnActivateSpeaker(EntityUid uid, RadioSpeakerComponent component, ActivateInWorldEvent args) { ToggleRadioSpeaker(uid, args.User, args.Handled, component); args.Handled = true; } public void ToggleRadioMicrophone(EntityUid uid, EntityUid user, bool quiet = false, RadioMicrophoneComponent? component = null) { if (!Resolve(uid, ref component)) return; if (component.PowerRequired && !this.IsPowered(uid, EntityManager)) return; SetMicrophoneEnabled(uid, !component.Enabled, component); if (!quiet) { var state = Loc.GetString(component.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, user); } if (component.Enabled) EnsureComp(uid).Range = component.ListenRange; else RemCompDeferred(uid); } private void OnGetVerbs(EntityUid uid, RadioMicrophoneComponent component, GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract || args.Hands == null) return; if (component.SupportedChannels == null || component.SupportedChannels.Count <= 1) return; if (component.PowerRequired && !this.IsPowered(uid, EntityManager)) return; foreach (var channel in component.SupportedChannels) { var proto = _protoMan.Index(channel); var v = new Verb { Text = proto.LocalizedName, Priority = 1, Category = VerbCategory.ChannelSelect, Disabled = component.BroadcastChannel == channel, DoContactInteraction = true, Act = () => { component.BroadcastChannel = channel; _popup.PopupEntity(Loc.GetString("handheld-radio-component-channel-set", ("channel", channel)), uid, args.User); } }; args.Verbs.Add(v); } } private void OnPowerChanged(EntityUid uid, RadioMicrophoneComponent component, ref PowerChangedEvent args) { if (args.Powered) return; SetMicrophoneEnabled(uid, false, component); } public void SetMicrophoneEnabled(EntityUid uid, bool enabled, RadioMicrophoneComponent? component = null) { if (!Resolve(uid, ref component, false)) return; component.Enabled = enabled; _appearance.SetData(uid, RadioDeviceVisuals.Broadcasting, component.Enabled); } public void ToggleRadioSpeaker(EntityUid uid, EntityUid user, bool quiet = false, RadioSpeakerComponent? component = null) { if (!Resolve(uid, ref component)) return; component.Enabled = !component.Enabled; if (!quiet) { var state = Loc.GetString(component.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, user); } if (component.Enabled) EnsureComp(uid).Channels.UnionWith(component.Channels); else RemCompDeferred(uid); } #endregion private void OnExamine(EntityUid uid, RadioMicrophoneComponent component, ExaminedEvent args) { if (!args.IsInDetailsRange) return; var proto = _protoMan.Index(component.BroadcastChannel); args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", proto.Frequency))); args.PushMarkup(Loc.GetString("handheld-radio-component-chennel-examine", ("channel", proto.LocalizedName))); } private void OnListen(EntityUid uid, RadioMicrophoneComponent component, ListenEvent args) { if (HasComp(args.Source)) return; // no feedback loops please. if (_recentlySent.Add((args.Message, args.Source))) _radio.SendRadioMessage(args.Source, args.Message, _protoMan.Index(component.BroadcastChannel), uid); } private void OnAttemptListen(EntityUid uid, RadioMicrophoneComponent component, ListenAttemptEvent args) { if (component.PowerRequired && !this.IsPowered(uid, EntityManager) || component.UnobstructedRequired && !_interaction.InRangeUnobstructed(args.Source, uid, 0)) { args.Cancel(); } } private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref RadioReceiveEvent args) { var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource)); RaiseLocalEvent(args.MessageSource, nameEv); var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)), ("originalName", nameEv.Name)); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false); } }