CanSpectateTarget
CanSpectateTarget is called when the game runs BasePlayer.UpdateSpectateTarget to resolve a name string into the player that a spectator is about to watch. Returning a non-null value stops the game's own resolution and leaves the spectate target unchanged.
object CanSpectateTarget(BasePlayer player, string strName)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The BasePlayer whose spectate target is being resolved, i.e. the player in spectator mode (a dead player or an admin using the spectate command). |
strName | string | The name or player ID string the game is trying to resolve into a spectate target. It is the raw argument passed to UpdateSpectateTarget, so it may match no online player. |
Return value
Returning null lets UpdateSpectateTarget run normally and resolve strName into a spectate target. Returning any non-null value (commonly true) makes the method return immediately, so the spectate target is not updated and no announce happens. The method returns void, so the value itself is ignored.
How it works
This hook fires inside BasePlayer.UpdateSpectateTarget(string strName, bool invalidateIfNone, bool announce), which the game calls when a spectator's target needs to be recomputed from a name string. That happens after a dead player cycles through spectate targets, when an admin issues a spectate command with a name, and on any internal re-resolve where the stored target name is looked up again.
The first argument is the player doing the spectating. The second is the raw name/ID string being resolved. The hook fires before the game matches that string against players and before it assigns the new target or announces it in chat.
Returning a non-null value (for example true) short-circuits the method, so the target is not changed for that call. Returning null lets the normal lookup proceed. Because the original method returns void, the returned value itself is discarded; only whether it is null matters.
The clean way to restrict who can be watched is to validate strName yourself and return a non-null value when it should be rejected, then let the game continue for accepted names. Since strName can be a partial name, an ID, or an empty string, match it against BasePlayer.activePlayerList and compare the resolved player, not the raw text.
Example plugin: Block spectating of players who opted out
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("SpectateOptOut", "RustPluginGenerator", "1.0.0")]
[Description("Lets players opt out of being chosen as a spectate target")]
public class SpectateOptOut : RustPlugin
{
private const string PermUse = "spectateoptout.use";
private const string DataFile = "SpectateOptOut/players";
private PluginConfig config;
private HashSet<string> optedOut;
private class PluginConfig
{
[JsonProperty("Warn spectators when blocked")]
public bool WarnSpectator { get; set; } = true;
}
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>
{
["OptedOut"] = "You are now hidden from spectating.",
["OptedIn"] = "You can be spectated again.",
["AlreadyOut"] = "You are already hidden from spectating.",
["AlreadyIn"] = "You are already visible to spectators.",
["Blocked"] = "{0} has disabled spectating.",
["NoPerm"] = "You do not have permission to use this command."
}, 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(PermUse, this);
LoadData();
}
private void LoadData()
{
optedOut = Interface.Oxide.DataFileSystem.ReadObject<HashSet<string>>(DataFile) ?? new HashSet<string>();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(DataFile, optedOut);
}
[ChatCommand("spectateoff")]
private void CmdSpectateOff(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
SendReply(player, Msg("NoPerm", player.UserIDString));
return;
}
if (!optedOut.Add(player.UserIDString))
{
SendReply(player, Msg("AlreadyOut", player.UserIDString));
return;
}
SaveData();
SendReply(player, Msg("OptedOut", player.UserIDString));
}
[ChatCommand("spectateon")]
private void CmdSpectateOn(BasePlayer player, string command, string[] args)
{
if (!optedOut.Remove(player.UserIDString))
{
SendReply(player, Msg("AlreadyIn", player.UserIDString));
return;
}
SaveData();
SendReply(player, Msg("OptedIn", player.UserIDString));
}
private object CanSpectateTarget(BasePlayer player, string strName)
{
if (player == null || string.IsNullOrEmpty(strName)) return null;
var target = FindTarget(strName);
if (target == null || !optedOut.Contains(target.UserIDString)) return null;
if (config.WarnSpectator && player.IsConnected)
SendReply(player, Msg("Blocked", player.UserIDString, target.displayName));
return true;
}
private BasePlayer FindTarget(string nameOrId)
{
foreach (var candidate in BasePlayer.activePlayerList)
{
if (candidate == null) continue;
if (candidate.UserIDString == nameOrId || candidate.displayName == nameOrId)
return candidate;
}
return null;
}
private void Unload()
{
SaveData();
}
}
}Players who want privacy can run /spectateoff, which stores their user ID in a data file and flips a flag. CanSpectateTarget is called on every spectate re-resolve: the plugin resolves strName against BasePlayer.activePlayerList (by display name or user ID) and, if that player is in the opt-out set, returns true to abort the target change and warns the spectator in chat through the language file. Everyone else returns null, so the game's own lookup continues untouched. /spectateon removes the flag, and data is saved on unload so the choice survives restarts.
Common use cases
Privacy-aware spectating
Let players opt out of being watched and refuse spectate target changes that point at them, while leaving normal spectating intact.
Restrict spectating to staff targets
Only allow spectators to land on players who have a specific permission, rejecting every other resolved name.
Anti-abuse spectate filter
Block spectating of players currently in a raid-safe zone, during an active event, or while flagged by another plugin, and warn the spectator why.
Pitfalls
strName may not match a player
The argument is the raw string passed to
UpdateSpectateTarget; it can be empty, a partial name, or an ID. Always resolve it againstactivePlayerListand null-check before deciding.Returning false does not block
The check is null-based. Returning
falseis a non-null value and will block, which is easy to misread; returnnullto allow andtrueto block for clarity.Blocking leaves the old target
The game simply returns, so the spectator keeps the previous target (or none). Send a chat message so the spectator understands why nothing changed.
Build a plugin with CanSpectateTarget
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 CanSpectateTarget
Generate