Skip to content
RustPluginGenerator

OnRfListenerAdded

OnRfListenerAdded is called after an object has successfully registered as a listener on an RF frequency through RFManager.AddListener. It only fires when the listener was actually added, so it never reports duplicates.

Signature
void OnRfListenerAdded(IRFObject obj, int frequency)

Parameters

Hook parameters
ParameterTypeDescription
objIRFObjectThe IRFObject that was just registered as a listener on the frequency. Cast it to BaseEntity (or a specific type such as RFReceiver, Door or BaseOven) to inspect or act on the entity.
frequencyintThe RF channel the listener was added to. Useful to allow or restrict specific channels and to correlate the listener with broadcasters on the same frequency.

Return value

The return value is ignored. The listener has already been added by the time the hook runs, so returning a value cannot undo the registration or change the frequency. To react to a listener being removed, use OnRfListenerRemove or OnRfListenerRemoved.

How it works

The hook fires inside RFManager.AddListener, after the game has confirmed the object is now part of that frequency's listener set. Registration happens when entities that receive RF signals become active: a powered RF receiver, an RF-triggered door or garage door, a base oven or other RF-controllable deployable. If the object was already listening on that frequency, GetListenerSet(frequency).Add(obj) returns false and neither the signal update nor this hook runs.

obj is the listener interface itself; cast it to BaseEntity to get OwnerID, position, prefab name or the building privilege it sits in. frequency is the channel number, which lets you allow only a set of frequencies, notify owners, or track channel usage per entity.

Because the parameter is IRFObject, you cannot narrow the dispatch by declaring a more specific type; instead filter with an as cast inside the hook. The hook can fire repeatedly for the same entity, since every activation cycle re-adds the listener. Keep the body cheap: it runs for every listener registration, including mass activations after a restart.

Example plugin: Log and alert on restricted RF channel listeners

RfChannelMonitor.cs
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Rf Channel Monitor", "RustPluginGenerator", "1.0.0")]
    [Description("Warns players when an RF listener registers on a restricted channel near their tracked position")]
    public class RfChannelMonitor : RustPlugin
    {
        private const string PermBypass = "rfchannelmonitor.bypass";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Restricted frequencies")]
            public List<int> RestrictedFrequencies = new List<int> { 4242, 7777 };

            [JsonProperty("Alert radius (meters)")]
            public float AlertRadius = 50f;

            [JsonProperty("Tracked position lifetime (seconds)")]
            public int TrackLifetime = 120;
        }

        private class StoredData
        {
            [JsonProperty("Tracked players")]
            public Dictionary<ulong, TrackedPlayer> Tracked = new Dictionary<ulong, TrackedPlayer>();
        }

        private class TrackedPlayer
        {
            [JsonProperty("Position")]
            public Vector3 Position;

            [JsonProperty("Timestamp")]
            public int Timestamp;
        }

        protected override void LoadDefaultConfig() => config = new PluginConfig();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<PluginConfig>();
                if (config == null) throw new Exception();
            }
            catch
            {
                PrintWarning("Configuration file is invalid, using defaults");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Alert"] = "RF listener detected on restricted channel {0} at {1}. A device near you is listening.",
                ["Owner"] = "Your device now listens on restricted channel {0}."
            }, this);
        }

        private string Msg(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);

        private void Init()
        {
            permission.RegisterPermission(PermBypass, this);
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private void Unload()
        {
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        private void OnPlayerSleepEnded(BasePlayer player)
        {
            if (player == null || player.IsNpc) return;
            data.Tracked[player.userID] = new TrackedPlayer
            {
                Position = player.transform.position,
                Timestamp = Facepunch.Math.Epoch.Current
            };
        }

        private void OnRfListenerAdded(IRFObject obj, int frequency)
        {
            if (config == null || !config.RestrictedFrequencies.Contains(frequency)) return;

            var entity = obj as BaseEntity;
            if (entity == null || entity.IsDestroyed) return;

            var owner = BasePlayer.FindByID(entity.OwnerID);
            if (owner != null && owner.IsConnected && !permission.UserHasPermission(owner.UserIDString, PermBypass))
            {
                owner.ChatMessage(Msg("Owner", owner.UserIDString, frequency));
                return;
            }

            Vector3 position = entity.transform.position;
            int now = Facepunch.Math.Epoch.Current;
            var stale = new List<ulong>();

            foreach (var pair in data.Tracked)
            {
                if (now - pair.Value.Timestamp > config.TrackLifetime)
                {
                    stale.Add(pair.Key);
                    continue;
                }

                if (Vector3.Distance(pair.Value.Position, position) > config.AlertRadius) continue;

                var player = BasePlayer.FindByID(pair.Key);
                if (player == null || !player.IsConnected) continue;
                if (permission.UserHasPermission(player.UserIDString, PermBypass)) continue;

                player.ChatMessage(Msg("Alert", player.UserIDString, frequency,
                    position.ToString("F0")));
            }

            foreach (var id in stale) data.Tracked.Remove(id);
        }
    }
}

The plugin keeps a configurable list of restricted RF frequencies. When OnRfListenerAdded fires it casts the IRFObject to a BaseEntity, resolves the owning player and checks the channel against the list. Allowed listeners are ignored. For a restricted channel the entity's position is compared with each owner that has been seen recently (stored with a timestamp from Facepunch.Math.Epoch.Current), and if the listener sits within the configured radius of another player's tracked position, that player receives a localized warning with the channel and coordinates. Data is written to oxide/data on unload and the tracked table is pruned of stale entries on each check.

Common use cases

  • Reserve channels for admins or events

    Compare frequency against a configured allow-list and warn or log when an entity registers on a reserved channel, for example 4242 used by admin-controlled doors.

  • Notify owners of RF-controlled devices

    Cast obj to BaseEntity, read OwnerID and send the owner a message confirming which channel their receiver, door or oven is listening on.

  • Detect proximity RF relays

    Track listener positions per frequency and flag when a listener is placed inside another player's base, which can indicate a remote trigger placed during a raid.

Pitfalls

  • The hook repeats on every activation

    Whenever an RF object is re-enabled, AddListener runs again and fires the hook. Deduplicate by net.ID and frequency if you send one-time notifications, otherwise owners get spammed.

  • Can't narrow by parameter type

    The parameter is IRFObject, not a concrete entity type, so declaring something stricter would never match. Do the cast to BaseEntity or Door inside the method and null-check the result.

  • Fires during load and mass activation

    After a restart or map load many listeners register at once. Avoid expensive work (entity scans, chat broadcasts to everyone) unless it is gated by a config flag or a small frequency filter.

Build a plugin with OnRfListenerAdded

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Reserve channels for admins or events using the OnRfListenerAdded hook, with a config file and a permission
Generate this plugin

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.

Build a plugin with OnRfListenerAdded

Generate