using System.Linq; 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.Power; using Content.Shared.Radio; using Content.Shared.Chat; using Content.Shared.Radio.Components; using Content.Shared.UserInterface; // Nuclear-14 using Content.Shared._NC.Radio; // Nuclear-14 using Robust.Server.GameObjects; // Nuclear-14 using Robust.Shared.Prototypes; using Content.Shared.Access.Systems; // Frontier using Content.Shared.Verbs; //Frontier using Robust.Shared.Utility; // Frontier using Content.Shared.ActionBlocker; //Frontier 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!; [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly AccessReaderSystem _access = default!; // Frontier: access [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; // Frontier // Used to prevent a shitter from using a bunch of radios to spam chat. private HashSet<(string, EntityUid, RadioChannelPrototype)> _recentlySent = new(); // Frontier: minimum, maximum radio frequencies private const int MinRadioFrequency = 1000; private const int MaxRadioFrequency = 3000; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMicrophoneInit); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnActivateMicrophone); SubscribeLocalEvent(OnListen); SubscribeLocalEvent(OnAttemptListen); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent>(OnGetAltVerbs); // Frontier SubscribeLocalEvent(OnSpeakerInit); SubscribeLocalEvent(OnActivateSpeaker); SubscribeLocalEvent(OnReceiveRadio); SubscribeLocalEvent(OnIntercomEncryptionChannelsChanged); SubscribeLocalEvent(OnToggleIntercomMic); SubscribeLocalEvent(OnToggleIntercomSpeaker); SubscribeLocalEvent(OnSelectIntercomChannel); // Nuclear-14-Start SubscribeLocalEvent(OnBeforeHandheldRadioUiOpen); SubscribeLocalEvent(OnToggleHandheldRadioMic); SubscribeLocalEvent(OnToggleHandheldRadioSpeaker); SubscribeLocalEvent(OnChangeHandheldRadioFrequency); // Nuclear-14-End SubscribeLocalEvent(OnMapInit); // Frontier } 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) { if (!args.Complex) return; if (!component.ToggleOnInteract) return; ToggleRadioMicrophone(uid, args.User, args.Handled, component); args.Handled = true; } private void OnActivateSpeaker(EntityUid uid, RadioSpeakerComponent component, ActivateInWorldEvent args) { if (!args.Complex) return; if (!component.ToggleOnInteract) return; 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; SetMicrophoneEnabled(uid, user, !component.Enabled, quiet, component); } private void OnPowerChanged(EntityUid uid, RadioMicrophoneComponent component, ref PowerChangedEvent args) { if (args.Powered) return; SetMicrophoneEnabled(uid, null, false, true, component); } public void SetMicrophoneEnabled(EntityUid uid, EntityUid? user, bool enabled, bool quiet = false, RadioMicrophoneComponent? component = null, bool force = false) // Frontier: add force { if (!Resolve(uid, ref component, false)) return; if (!force && component.PowerRequired && !this.IsPowered(uid, EntityManager)) // Frontier: add force return; component.Enabled = enabled; if (!quiet && user != null) { 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.Value, user.Value); } _appearance.SetData(uid, RadioDeviceVisuals.Broadcasting, component.Enabled); if (component.Enabled) EnsureComp(uid).Range = component.ListenRange; else RemCompDeferred(uid); } public void ToggleRadioSpeaker(EntityUid uid, EntityUid user, bool quiet = false, RadioSpeakerComponent? component = null) { if (!Resolve(uid, ref component)) return; SetSpeakerEnabled(uid, user, !component.Enabled, quiet, component); } public void SetSpeakerEnabled(EntityUid uid, EntityUid? user, bool enabled, bool quiet = false, RadioSpeakerComponent? component = null) { if (!Resolve(uid, ref component)) return; component.Enabled = enabled; if (!quiet && user != null) { 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.Value, user.Value); } _appearance.SetData(uid, RadioDeviceVisuals.Speaker, component.Enabled); 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); using (args.PushGroup(nameof(RadioMicrophoneComponent))) { args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", /*Nuclear-14-start*/ component.Frequency /*Nuclear-14-end*/))); 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. var channel = _protoMan.Index(component.BroadcastChannel)!; if (_recentlySent.Add((args.Message, args.Source, channel))) _radio.SendRadioMessage(args.Source, args.Message, channel, uid, /*Nuclear-14-start*/ frequency: component.Frequency,/*Nuclear-14-end*/ languageOverride: args.Language); // Horizon language } 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) { if (uid == 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(uid)), ("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(uid, args.Message, component.OutputChatType, ChatTransmitRange.GhostRangeLimitNoAdminCheck, nameOverride: name, checkRadioPrefix: false, language: args.Language); // Frontier: GhostRangeLimit 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); } private void OnToggleIntercomMic(Entity ent, ref ToggleIntercomMicMessage args) { if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager)) return; if (!_access.IsAllowed(args.Actor, ent.Owner) || !_actionBlocker.CanComplexInteract(args.Actor)) // Frontier return; // Frontier SetMicrophoneEnabled(ent, args.Actor, args.Enabled, true); ent.Comp.MicrophoneEnabled = args.Enabled; Dirty(ent); } private void OnToggleIntercomSpeaker(Entity ent, ref ToggleIntercomSpeakerMessage args) { if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager)) return; if (!_access.IsAllowed(args.Actor, ent.Owner) || !_actionBlocker.CanComplexInteract(args.Actor)) // Frontier return; // Frontier SetSpeakerEnabled(ent, args.Actor, args.Enabled, true); ent.Comp.SpeakerEnabled = args.Enabled; Dirty(ent); } private void OnSelectIntercomChannel(Entity ent, ref SelectIntercomChannelMessage args) { if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager)) return; if (!_access.IsAllowed(args.Actor, ent.Owner) || !_actionBlocker.CanComplexInteract(args.Actor)) // Frontier return; // Frontier if (!_protoMan.TryIndex(args.Channel, out var channel) || !ent.Comp.SupportedChannels.Contains(args.Channel)) // Nuclear-14: add channel return; SetIntercomChannel(ent, args.Channel); } private void SetIntercomChannel(Entity ent, ProtoId? channel) { ent.Comp.CurrentChannel = channel; if (channel == null) { SetSpeakerEnabled(ent, null, false); SetMicrophoneEnabled(ent, null, false); ent.Comp.MicrophoneEnabled = false; ent.Comp.SpeakerEnabled = false; Dirty(ent); return; } if (TryComp(ent, out var mic)) { mic.BroadcastChannel = channel; if(_protoMan.TryIndex(channel, out var channelProto)) // Frontier mic.Frequency = channelProto.Frequency; // Frontier } if (TryComp(ent, out var speaker)) speaker.Channels = new() { channel }; Dirty(ent); } // Nuclear-14-Start #region Handheld Radio private void OnBeforeHandheldRadioUiOpen(Entity microphone, ref BeforeActivatableUIOpenEvent args) { UpdateHandheldRadioUi(microphone); } private void OnToggleHandheldRadioMic(Entity microphone, ref ToggleHandheldRadioMicMessage args) { if (!args.Actor.Valid) return; SetMicrophoneEnabled(microphone, args.Actor, args.Enabled, true); UpdateHandheldRadioUi(microphone); } private void OnToggleHandheldRadioSpeaker(Entity microphone, ref ToggleHandheldRadioSpeakerMessage args) { if (!args.Actor.Valid) return; SetSpeakerEnabled(microphone, args.Actor, args.Enabled, true); UpdateHandheldRadioUi(microphone); } private void OnChangeHandheldRadioFrequency(Entity microphone, ref SelectHandheldRadioFrequencyMessage args) { if (!args.Actor.Valid) return; // Update frequency if valid and within range. if (args.Frequency >= MinRadioFrequency && args.Frequency <= MaxRadioFrequency) microphone.Comp.Frequency = args.Frequency; // Update UI with current frequency. UpdateHandheldRadioUi(microphone); } private void UpdateHandheldRadioUi(Entity radio) { var speakerComp = CompOrNull(radio); var frequency = radio.Comp.Frequency; var micEnabled = radio.Comp.Enabled; var speakerEnabled = speakerComp?.Enabled ?? false; var state = new HandheldRadioBoundUIState(micEnabled, speakerEnabled, frequency); if (TryComp(radio, out var uiComp)) _ui.SetUiState((radio.Owner, uiComp), HandheldRadioUiKey.Key, state); // Frontier: TrySetUiState /// Adds an alt verb allowing for the mic to be toggled easily. /// private void OnGetAltVerbs(EntityUid uid, RadioMicrophoneComponent microphone, GetVerbsEvent args) { if (!args.CanInteract || !args.CanAccess) return; if (!_access.IsAllowed(args.User, uid) || !_actionBlocker.CanComplexInteract(args.User)) return; AlternativeVerb verb = new() { Text = Loc.GetString("handheld-radio-component-toggle"), Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), Act = () => ToggleRadioOrIntercomMic(uid, microphone, args.User) }; args.Verbs.Add(verb); } /// /// A mic toggle for both radios and intercoms. /// private void ToggleRadioOrIntercomMic(EntityUid uid, RadioMicrophoneComponent microphone, EntityUid user) { if (!_access.IsAllowed(user, uid)) return; if (microphone.PowerRequired && !this.IsPowered(uid, EntityManager)) return; ToggleRadioMicrophone(uid, user, false, microphone); if (TryComp(uid, out var intercom)) { intercom.MicrophoneEnabled = microphone.Enabled; Dirty((uid, intercom)); } } // Frontier End // Frontier: init intercom with map private void OnMapInit(EntityUid uid, IntercomComponent ent, MapInitEvent args) { // Set initial frequency (must be done regardless of power/enabled) if (ent.CurrentChannel != null && _protoMan.TryIndex(ent.CurrentChannel, out var channel) && TryComp(uid, out RadioMicrophoneComponent? mic)) { mic.Frequency = channel.Frequency; } if (ent.StartSpeakerOnMapInit) { SetSpeakerEnabled(uid, null, true); ent.SpeakerEnabled = true; _appearance.SetData(uid, RadioDeviceVisuals.Speaker, true); } if (ent.StartMicrophoneOnMapInit) { SetMicrophoneEnabled(uid, null, true, force: true); ent.MicrophoneEnabled = true; _appearance.SetData(uid, RadioDeviceVisuals.Broadcasting, true); } } // End Frontier }