OnPhotoCaptured
OnPhotoCaptured is called after a player successfully takes a photo with an Instant Camera, once the image bytes exist and the camera item has lost one point of condition. The hook is informational only and cannot cancel the shot.
void OnPhotoCaptured(PhotoEntity photoEntity, Item item, BasePlayer player, byte[] array)Parameters
| Parameter | Type | Description |
|---|---|---|
photoEntity | PhotoEntity | The PhotoEntity object backing the photo that was just taken; it carries the photo's render target data and lives on the camera's item side. Null-check it before touching anything on it. |
item | Item | The Instant Camera item the player used. Its condition has already been reduced by the shot when the hook fires, and it identifies the skin/deployed state of the camera. |
player | BasePlayer | The BasePlayer who pressed the shutter. Use it for permission checks, chat feedback and per-player cooldowns. |
array | byte[] | The raw byte array of the captured image data (the JPEG bytes written to the photo). Useful for size checks, storage, or external processing. |
Return value
The return value is ignored at the injected call site in InstantCameraTool.TakePhoto. Returning anything (including null) has no effect on the game: the photo is always committed and the camera always loses condition.
How it works
The hook fires at the very end of InstantCameraTool.TakePhoto, after the game has already pooled its temporary objects, deducted one condition point from the camera item, and assembled the photo byte array. It fires only on a successful capture, not when the shutter fails (empty photo slots, blocked use, or a dead/disconnected player). photoEntity is the PhotoEntity the game created for the image, item is the camera item, player is the shooter, and array is the encoded image payload.
The return value is ignored, so the shot always completes: you cannot refund the condition point, block the capture, or replace the byte array from this hook. Treat it as a notification. If you only care about a specific camera item (for example a custom skin), declare item normally and check item.skin, or read photoEntity/player to attribute the shot. Because the hook fires once per shutter press, it is cheap, but keep work inside it small and never call Puts or PrintWarning there.
To stop photos before they happen, OnPhotoCapture and CanUseLockedEntity-style pre-checks are the available approach; OnPhotoCaptured only observes.
Example plugin: Award a daily limit bonus for camera shots
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Photo Shot Reward", "RustPluginGenerator", "1.0.0")]
[Description("Grants a limited reward each time a player takes a photo with the Instant Camera")]
public class PhotoShotReward : RustPlugin
{
private const string PermUse = "photoshotreward.use";
private PluginConfig config;
private PhotoData data;
private readonly Dictionary<ulong, float> cooldowns = new Dictionary<ulong, float>();
private Timer saveTimer;
private class PluginConfig
{
[JsonProperty("Reward item shortname")]
public string RewardShortname = "scrap";
[JsonProperty("Reward amount")]
public int RewardAmount = 5;
[JsonProperty("Daily limit")]
public int DailyLimit = 20;
[JsonProperty("Cooldown seconds")]
public float CooldownSeconds = 10f;
}
private class PhotoData
{
[JsonProperty("Captures per player")]
public Dictionary<ulong, int> Captures = new Dictionary<ulong, int>();
}
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>
{
["Reward"] = "Photo captured: {0} x {1}. Today: {2}/{3}.",
["Limit"] = "Photo captured, but you reached your daily limit of {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(PermUse, this);
data = Interface.Oxide.DataFileSystem.ReadObject<PhotoData>(Name) ?? new PhotoData();
saveTimer = timer.Every(300f, SaveData);
}
private void Unload()
{
saveTimer?.Destroy();
SaveData();
}
private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, data);
private void OnPhotoCaptured(PhotoEntity photoEntity, Item item, BasePlayer player, byte[] array)
{
if (player == null || item == null || item.info == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse)) return;
float now = Time.realtimeSinceStartup;
float last;
if (cooldowns.TryGetValue(player.userID, out last) && now - last < config.CooldownSeconds) return;
cooldowns[player.userID] = now;
ulong userId = player.userID;
int count;
data.Captures.TryGetValue(userId, out count);
if (count >= config.DailyLimit)
{
player.ChatMessage(Msg("Limit", player.UserIDString, config.DailyLimit));
return;
}
count++;
data.Captures[userId] = count;
Item reward = ItemManager.CreateByName(config.RewardShortname, config.RewardAmount);
if (reward != null)
{
player.GiveItem(reward);
player.ChatMessage(Msg("Reward", player.UserIDString, config.RewardAmount, reward.info.displayName.english, count, config.DailyLimit));
}
}
}
}The plugin rewards players for taking photos with the Instant Camera. When OnPhotoCaptured fires, it first null-checks the player and camera item, then verifies the player holds the configured permission. A per-player cooldown stored in a nested timer registry stops rapid shutter spam from paying out repeatedly. Eligible captures are counted, the capture count is checked against DailyLimit in the config, and only within-limit shots grant the configured item via ItemManager.CreateByName and player.GiveItem. A chat message from the language file reports progress. Data is persisted through Interface.Oxide.DataFileSystem in oxide/data, saved on unload and periodically.
Common use cases
Reward photographers with items or currency
Grant a small payout per shot, gated by permission and a per-player cooldown so players cannot farm the camera by spamming the shutter.
Log and moderate photo activity
Record who captured a photo, the byte size of the image and the camera skin to a data file to investigate reports of abusive photography on PvP servers.
Enforce daily capture quotas
Count captures per player per day and stop paying out after a configured limit, resetting the counter with a timer at server restart or a rolling window.
Pitfalls
The hook cannot cancel the photo
The return value is ignored. If you need to prevent photos entirely, this hook is the wrong place; the capture already happened when it runs.
The camera already lost condition
One condition point is deducted before
OnPhotoCapturedfires. Do not try to refund it from this hook expecting a different result; heal or repair the item directly if that is a feature you want.Avoid chat spam
Players can press the shutter repeatedly. Always gate feedback with a cooldown, otherwise reward messages flood the chat and turn a convenience into an annoyance.
Build a plugin with OnPhotoCaptured
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 OnPhotoCaptured
Generate