OnPhotoCapture
OnPhotoCapture is called by InstantCameraTool.TakePhoto just before the captured image is written to the PhotoEntity and the photo is registered with analytics. Returning a non-null value cancels the save, so the photo never gets stored.
object OnPhotoCapture(PhotoEntity photoEntity, Item item, BasePlayer player, byte[] array)Parameters
| Parameter | Type | Description |
|---|---|---|
photoEntity | PhotoEntity | The PhotoEntity being written to. It holds the item's image data and ownership; useful for checking the target, reading its owner or current image state. |
item | Item | The camera photo item the player is capturing into. Check item.info.shortname, skin or amount, or Remove/MarkDirty it to affect the result. |
player | BasePlayer | The BasePlayer holding the camera and taking the photo. Can be null in edge cases, so null-check before using UserIDString or inventory. |
array | byte[] | The raw JPEG byte array produced by the camera. Use its Length to enforce size limits; do not assume it is valid image data. |
Return value
Return null to let the capture proceed: SetImageData, analytics and any post-capture logic run normally. Return any non-null value to return early from TakePhoto before SetImageData and the analytics call, so the photo item keeps its previous (or blank) image and the player gets no stored photo.
How it works
The hook fires inside InstantCameraTool.TakePhoto at the point where the camera has already produced the JPEG bytes and resolved the target PhotoEntity from the held item. It runs before photoEntity.SetImageData(player.userID, array) and before the Azure UGC analytics call, so it is the last chance to inspect or block a capture.
photoEntity is the entity that would receive the image data. item is the photo item itself, which you can inspect for shortname, skin or stack size. player is the photographer; treat it as possibly null and check IsConnected before sending anything to them. array is the raw bytes the camera produced, so array.Length is a cheap way to enforce a maximum file size without decoding the image.
The return value is decisive: any non-null return stops the capture. A false or an empty string both cancel it because Oxide only skips a value-typed result when the declared type differs; here the method returns void, so the exact non-null value does not matter.
Because it is called only when a player actually takes a photo, it is safe to run heavier checks here (permissions, cooldowns, byte-size limits) than in per-tick hooks.
Example plugin: Enforce a photo size cap and a per-player cooldown
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Photo Capture Guard", "RustPluginGenerator", "1.0.0")]
[Description("Enforces a maximum photo size and a per-player cooldown before a camera photo is stored.")]
public class PhotoCaptureGuard : RustPlugin
{
private const string PermUse = "photocaptureguard.use";
private class PluginConfig
{
[JsonProperty("Maximum photo size in bytes")]
public int MaxBytes = 98304;
[JsonProperty("Capture cooldown in seconds")]
public float CooldownSeconds = 3f;
[JsonProperty("Require permission")]
public bool RequirePermission = false;
}
private PluginConfig config;
private readonly Dictionary<string, float> lastCapture = new Dictionary<string, float>();
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>
{
["NoPermission"] = "You do not have permission to take photos.",
["TooLarge"] = "That photo is too large ({0} bytes, limit {1}).",
["Cooldown"] = "Wait {0:0.0}s before taking another photo."
}, 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);
}
private object OnPhotoCapture(PhotoEntity photoEntity, Item item, BasePlayer player, byte[] array)
{
if (photoEntity == null || item == null || player == null || array == null)
return null;
string userId = player.UserIDString;
if (config.RequirePermission && !permission.UserHasPermission(userId, PermUse))
{
player.ChatMessage(Msg("NoPermission", userId));
return true;
}
if (config.MaxBytes > 0 && array.Length > config.MaxBytes)
{
player.ChatMessage(Msg("TooLarge", userId, array.Length, config.MaxBytes));
return true;
}
float now = Time.realtimeSinceStartup;
float last;
if (config.CooldownSeconds > 0f && lastCapture.TryGetValue(userId, out last))
{
float remaining = config.CooldownSeconds - (now - last);
if (remaining > 0f)
{
player.ChatMessage(Msg("Cooldown", userId, remaining));
return true;
}
}
lastCapture[userId] = now;
return null;
}
private void Unload()
{
lastCapture.Clear();
}
}
}The plugin hooks OnPhotoCapture and checks two rules before letting a photo be stored. First, it compares array.Length against a configurable maximum (default 96 KB) and cancels oversized captures, refunding a short chat warning. Second, it keeps a per-player cooldown using Time.realtimeSinceStartup, cancelling rapid-fire shots so players cannot spam the server with photo writes. Both limits come from the config file, and the player-facing text is delivered through the language file. Returning null after both checks pass lets the vanilla capture proceed untouched; returning true on either rejection aborts SetImageData.
Common use cases
Limit storage-heavy photo captures
Reject photos larger than a configured byte threshold before
SetImageDataruns, keeping database and network updates bounded on busy servers.Add a photography cooldown
Track the last capture time per player ID and cancel captures inside the cooldown window to stop rapid-fire camera spam.
Restrict photos to permission holders
Call
permission.UserHasPermission(player.UserIDString, "photocaptureguard.use")and return non-null when it fails so unauthorised players cannot store images.
Pitfalls
Any non-null return cancels the capture
Because
TakePhotoreturnsvoid, any non-null value stops the write — includingfalseor0. Returnnullwhen you want to allow the photo.Do not assume player is non-null
Handle a null
playerdefensively;UserIDStringand messaging will throw otherwise. Bail out withnullif there is no reliable player context.Cooldown state is lost on reload
Keeping capture timestamps in memory is fine for simple throttles but resets on plugin reload. Persist via
Interface.Oxide.DataFileSystemif the limit must survive restarts.
Build a plugin with OnPhotoCapture
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 OnPhotoCapture
Generate