OnMetalDetectorFlagRequest
OnMetalDetectorFlagRequest is called when a player marks a position on a deployed metal detector, before the server checks that position against the nearest buried item. It fires for every flag RPC after the player and safe-zone checks pass.
void OnMetalDetectorFlagRequest(BaseMetalDetector metalDetector, UnityEngine.Vector3 position, BasePlayer player)Parameters
| Parameter | Type | Description |
|---|---|---|
metalDetector | BaseMetalDetector | The BaseMetalDetector entity whose client-side flag placement triggered the RPC. Use it to compare identity, read its position or flags, or ignore players scanning with a specific detector. |
position | UnityEngine.Vector3 | The world position the player flagged on the detector, read from the RPC stream. This is the point the server later validates against the nearest buried source before calling Detected. |
player | BasePlayer | The BasePlayer who sent the flag request. Already verified as non-null and outside a safe zone before the hook fires. |
Return value
The return value is ignored at this injection site. The game proceeds to VerifyScanPosition and possibly Detected regardless of what the hook returns, so returning a value has no effect on the scan.
How it works
The hook fires inside BaseMetalDetector.RPC_RequestFlag, which the client invokes when the player places a flag marker on the metal detector screen. Before the hook runs, the server has already read the RPC's player reference, rejected null players and players standing in a safe zone, and confirmed a nearby source exists. The flagged world position is read from the RPC stream and passed to the hook as position.
After the hook returns, the game calls VerifyScanPosition with the player's transform position and the flagged point. When the flagged position is close enough to the buried source, the server calls Detected on that source, which is what makes the detector ping and the item become discoverable. Declaring player as BasePlayer keeps the signature exact; the argument is always a player here, so type narrowing is not needed. metalDetector is always a live BaseMetalDetector instance.
Use the hook to observe or enforce rules around scanning: rate limiting, region restrictions, logging flagged points for admins, or blocking flags from players who are not authorized on the surrounding building. The hook cannot cancel the scan, only run logic around it.
Example plugin: Rate-limit metal detector flags and log results
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Metal Detector Guard", "RustPluginGenerator", "1.0.0")]
[Description("Rate-limits metal detector flag requests and reports flag counts to admins")]
public class MetalDetectorGuard : RustPlugin
{
private const string PermAdmin = "metaldetectorguard.admin";
private PluginConfig config;
private readonly Dictionary<ulong, FlagEntry> flags = new Dictionary<ulong, FlagEntry>();
private Timer pruneTimer;
private class PluginConfig
{
[JsonProperty("Seconds between allowed flags")]
public float CooldownSeconds = 3f;
[JsonProperty("Warn player when cooldown is hit")]
public bool WarnPlayer = true;
}
private class FlagEntry
{
public int Count;
public float LastFlag;
}
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>
{
["Cooldown"] = "Slow down, you can flag again in {0:0.0}s.",
["Report"] = "{0} has flagged {1} time(s) on metal detectors.",
["NoData"] = "No flag data for that player yet."
}, 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(PermAdmin, this);
}
private void OnServerInitialized()
{
pruneTimer = timer.Every(300f, PruneStaleEntries);
}
private void Unload()
{
pruneTimer?.Destroy();
}
private void OnMetalDetectorFlagRequest(BaseMetalDetector metalDetector, UnityEngine.Vector3 position, BasePlayer player)
{
if (player == null || metalDetector == null) return;
ulong id = player.userID.Get();
float now = UnityEngine.Time.realtimeSinceStartup;
FlagEntry entry;
if (!flags.TryGetValue(id, out entry))
{
entry = new FlagEntry();
flags[id] = entry;
}
entry.Count++;
if (entry.LastFlag > 0f && now - entry.LastFlag < config.CooldownSeconds)
{
if (config.WarnPlayer)
{
float remaining = config.CooldownSeconds - (now - entry.LastFlag);
player.ChatMessage(Msg("Cooldown", player.UserIDString, remaining));
}
return;
}
entry.LastFlag = now;
}
[ChatCommand("mdg")]
private void CmdMetalDetectorGuard(BasePlayer player, string command, string[] args)
{
if (player == null || !permission.UserHasPermission(player.UserIDString, PermAdmin)) return;
if (args == null || args.Length == 0)
{
SendReply(player, "/mdg <nameOrId>");
return;
}
BasePlayer target = BasePlayer.FindAwakeOrSleeping(args[0]);
if (target == null)
{
SendReply(player, Msg("NoData", player.UserIDString));
return;
}
FlagEntry entry;
if (!flags.TryGetValue(target.userID.Get(), out entry))
{
SendReply(player, Msg("NoData", player.UserIDString));
return;
}
SendReply(player, Msg("Report", player.UserIDString, target.displayName, entry.Count));
}
private void PruneStaleEntries()
{
float now = UnityEngine.Time.realtimeSinceStartup;
List<ulong> stale = null;
foreach (KeyValuePair<ulong, FlagEntry> kvp in flags)
{
if (now - kvp.Value.LastFlag > 1800f)
{
if (stale == null) stale = new List<ulong>();
stale.Add(kvp.Key);
}
}
if (stale == null) return;
foreach (ulong id in stale) flags.Remove(id);
}
}
}MetalDetectorGuard tracks how often each player flags on a detector. When OnMetalDetectorFlagRequest fires, the plugin checks the player against a configurable cooldown. Players who exceed the limit get a localized warning in chat, and the event is recorded in a small per-player counter that resets on cooldown expiry. The plugin does not block the scan itself because the hook cannot cancel it; instead it makes the cooldown visible to players and gives admins a configurable window. A short timer prunes stale entries, and everything is destroyed in Unload. Admins can inspect counters with a chat command gated by permission, which also lets them adjust the cooldown via config on next reload.
Common use cases
Detector spam prevention
Track how often each player flags a detector and warn or report them when they exceed a configurable rate, useful on servers where spammy flagging floods the chat or causes load.
Arena or event restrictions
Only allow flagging inside a defined region or during an event window. Combine with a position check to detect detectors deployed outside the intended area.
Admin audit trail
Log every flagged position with the detector's coordinates so admins can review how players are finding buried loot, or to spot automated scanning patterns.
Pitfalls
You cannot cancel the scan
The return value is ignored. If you need to stop the detector from marking a source, adjust the detector's state, revoke player access, or move the source elsewhere, not this hook.
Safe zones are already filtered
The hook only fires for players outside safe zones with a nearby source present. Do not add redundant checks for those conditions; they have already been applied.
Hook fires per flag, not per detector
A single player can flag many times in a row. If you keep per-player state, remember to prune it, and avoid heavy work inside the hook body.
Build a plugin with OnMetalDetectorFlagRequest
Describe what should happen and get a compiled plugin that uses this hook.
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 OnMetalDetectorFlagRequest
Generate