OnItemResearched
OnItemResearched is called when a ResearchTable finishes a research attempt, right before the game compares the player's scrap against the required research cost.
object OnItemResearched(ResearchTable researchTable, int scrapCost)Parameters
| Parameter | Type | Description |
|---|---|---|
researchTable | ResearchTable | The ResearchTable entity that finished the research attempt. Use it to read its position, owner (OwnerID) or to check which player opened it via the table's inventory if needed. |
scrapCost | int | The amount of scrap the game calculated as the research price for the target item (the value of ResearchTable.ScrapForResearch for that item). Returning a different int replaces this cost before the scrap check runs. |
Return value
Return null or anything that is not an int to leave the game's computed scrap cost unchanged. Return an int to replace scrapCost before the game checks whether the table's scrap is sufficient and consumes it.
How it works
The hook fires inside ResearchTable.ResearchAttemptFinished, after the game has resolved the target item and the scrap item in the table and computed the research cost. It only runs when both a target item and a scrap item are present, so it does not fire for empty or partially loaded tables. The researchTable parameter is the entity itself, and scrapCost is the scrap amount the game intends to charge.
The returned value is read as an int: returning a number replaces scrapCost for the upcoming scrapItem.amount >= num comparison and the downstream consume logic. Returning null (or any non-int) keeps the vanilla cost. This makes the hook the right place to apply discounts, surcharges or free research events.
Because the method signature takes ResearchTable and int, there is no useful narrowing to do — declare those exact types. The hook fires once per completed attempt, so it is not hot; still, avoid expensive work inside it. Note that the hook runs before scrap is consumed, so a lower value can turn an unaffordable attempt into a successful one.
Example plugin: Discount research cost for subscribers
using System;
using Newtonsoft.Json;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Research Discount", "RustPluginGenerator", "1.0.0")]
[Description("Scales research table scrap cost by permission group.")]
public class ResearchDiscount : RustPlugin
{
private const string PermDiscount = "researchdiscount.discount";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Discount percent for permission holders")]
public int DiscountPercent = 50;
[JsonProperty("Surcharge percent for everyone else")]
public int SurchargePercent = 100;
}
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);
private void Init()
{
permission.RegisterPermission(PermDiscount, this);
}
private object OnItemResearched(ResearchTable researchTable, int scrapCost)
{
if (researchTable == null || scrapCost <= 0) return null;
var player = BasePlayer.FindByID(researchTable.OwnerID);
if (player == null) return null;
int percent = permission.UserHasPermission(player.UserIDString, PermDiscount)
? config.DiscountPercent
: config.SurchargePercent;
if (percent < 0) percent = 0;
// Scale the vanilla cost; ceil so fractional scrap rounds up.
float scaled = scrapCost * (percent / 100f);
return Mathf.CeilToInt(scaled);
}
}
}The plugin lowers the scrap cost of every research attempt for players in a configured permission group and raises it for everyone else. OnItemResearched reads the table's OwnerID, resolves the player with BasePlayer.FindByID, and checks research.discount. Members pay discountPercent of the vanilla cost; non-members pay surchargePercent, acting as a soft sink. Arithmetic is rounded up with Mathf.CeilToInt so free research is only possible with a 0 percent setting. The plugin logs nothing per attempt and keeps no timers, so it is effectively stateless. Config holds the percentages so admins can tune them with oxide.reload.
Common use cases
Free research weekends
Return 0 from the hook while a flag in your config is enabled so players research blueprints without spending scrap. Disable the flag and reload to restore vanilla pricing.
VIP scrap discounts
Check
permission.UserHasPermissionon the player who owns the table and return a reduced integer cost. The game then only consumes the reduced amount from the scrap stack.Surcharge on expensive blueprints
Raise the cost for high-tier items by reading the target item through
researchTable.inventoryand returning a larger value. Players with insufficient scrap simply see the attempt fail as usual.
Pitfalls
OwnerID can be zero or an offline player
Tables placed before ownership transfers or owned by a now-sleeping player still fire the hook. Resolve with
BasePlayer.FindByIDand guard against null before checking permissions.Changing the cost does not refund anything
The hook runs before the scrap check and consumption. Returning a higher number only makes the attempt fail if the player lacks scrap; it does not deduct extra scrap on its own if the stack is short.
Only fires when both slots are filled
The game computes the target and scrap items before calling the hook. If either slot is empty the hook is skipped entirely, so do not rely on it for cleanup logic.
Build a plugin with OnItemResearched
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 OnItemResearched
Generate