How to make a Rust farming yield plugin (per-crop multipliers)
Multiplies the amount of crops a player receives when harvesting planter crops or wild plants, with a separate multiplier per item shortname. It uses OnGrowableGathered to change the harvested Item amount before it enters the player's inventory.
Planter boxes and wild hemp patches are the main source of cloth and food on most servers, and tuning that output usually means wiping and hand-editing loot tables or installing a full farming overhaul. This plugin does one job: it scales the number of items you get from a single harvest, per crop.
The OnGrowableGathered hook fires after the game has created the harvested Item but before it is handed to the player, so changing item.amount there is all that is needed. A config dictionary maps item shortnames such as cloth, corn, pumpkin, berry.blue or clone.hemp to a float multiplier, plus a global fallback and an optional cap so a 5x hemp setting cannot overflow the stack in one pickup. Admin chat commands let you change a multiplier or reload the config without restarting the server.
The plugin code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Farming Yield Multiplier", "RustPluginGenerator", "1.0.0")]
[Description("Multiplies crops harvested from planters and wild plants with per-item multipliers.")]
public class FarmingYieldMultiplier : RustPlugin
{
private const string PermAdmin = "farmingyieldmultiplier.admin";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Default multiplier")]
public float DefaultMultiplier = 1f;
[JsonProperty("Max amount per harvest")]
public int MaxAmountPerHarvest = 1000;
[JsonProperty("Notify player on bonus")]
public bool NotifyPlayer = true;
[JsonProperty("Item multipliers (shortname)")]
public Dictionary<string, float> Multipliers = new Dictionary<string, float>
{
["cloth"] = 1f,
["corn"] = 1f,
["pumpkin"] = 1f,
["mushroom"] = 1f,
["clone.hemp"] = 1f,
["berry.blue"] = 1f,
["berry.red"] = 1f,
["berry.white"] = 1f,
["berry.yellow"] = 1f,
["berry.green"] = 1f
};
}
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();
}
if (config.Multipliers == null) config.Multipliers = new Dictionary<string, float>();
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 use this command.",
["Usage"] = "Usage: /farmyield <shortname> <multiplier>, /farmyield list, /farmyield reload",
["Set"] = "Multiplier for {0} set to {1}x.",
["InvalidItem"] = "'{0}' is not a valid item shortname.",
["InvalidNumber"] = "'{0}' is not a valid multiplier.",
["List"] = "Configured multipliers: {0}",
["ListEmpty"] = "No multipliers are configured.",
["Reloaded"] = "Configuration reloaded.",
["Bonus"] = "+{0}x {1}"
}, 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()
{
foreach (var pair in config.Multipliers)
{
if (ItemManager.FindItemDefinition(pair.Key) == null)
PrintWarning($"Configured shortname '{pair.Key}' is not a valid item and will be ignored");
}
}
private void OnGrowableGathered(GrowableEntity growableEntity, Item item, BasePlayer player)
{
if (item == null || item.info == null) return;
float multiplier;
if (!config.Multipliers.TryGetValue(item.info.shortname, out multiplier))
multiplier = config.DefaultMultiplier;
if (multiplier <= 0f || Mathf.Approximately(multiplier, 1f)) return;
int baseAmount = item.amount;
int amount = Mathf.RoundToInt(baseAmount * multiplier);
if (amount < 1) amount = 1;
int cap = config.MaxAmountPerHarvest;
if (cap > 0 && amount > cap) amount = cap;
if (item.info.stackable > 0 && amount > item.info.stackable) amount = item.info.stackable;
if (amount == baseAmount) return;
item.amount = amount;
item.MarkDirty();
if (config.NotifyPlayer && player != null && amount > baseAmount)
SendReply(player, Msg("Bonus", player.UserIDString, amount - baseAmount, item.info.displayName.english));
}
[ChatCommand("farmyield")]
private void CmdFarmYield(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
if (args == null || args.Length == 0)
{
player.ChatMessage(Msg("Usage", player.UserIDString));
return;
}
if (args[0].Equals("list", StringComparison.OrdinalIgnoreCase))
{
if (config.Multipliers.Count == 0)
{
player.ChatMessage(Msg("ListEmpty", player.UserIDString));
return;
}
var entries = new List<string>();
foreach (var pair in config.Multipliers)
entries.Add($"{pair.Key}: {pair.Value}x");
entries.Sort(StringComparer.OrdinalIgnoreCase);
player.ChatMessage(Msg("List", player.UserIDString, string.Join(", ", entries)));
return;
}
if (args[0].Equals("reload", StringComparison.OrdinalIgnoreCase))
{
LoadConfig();
player.ChatMessage(Msg("Reloaded", player.UserIDString));
return;
}
if (args.Length < 2)
{
player.ChatMessage(Msg("Usage", player.UserIDString));
return;
}
string shortname = args[0].ToLowerInvariant();
if (ItemManager.FindItemDefinition(shortname) == null)
{
player.ChatMessage(Msg("InvalidItem", player.UserIDString, shortname));
return;
}
float multiplier;
if (!float.TryParse(args[1], out multiplier) || multiplier <= 0f)
{
player.ChatMessage(Msg("InvalidNumber", player.UserIDString, args[1]));
return;
}
config.Multipliers[shortname] = multiplier;
SaveConfig();
player.ChatMessage(Msg("Set", player.UserIDString, shortname, multiplier));
}
}
}Hooks used: OnServerInitialized, OnGrowableGathered
Config file
Created automatically on first load at oxide/config/FarmingYieldMultiplier.json.
{
"Default multiplier": 1,
"Max amount per harvest": 1000,
"Notify player on bonus": true,
"Item multipliers (shortname)": {
"cloth": 1,
"corn": 1,
"pumpkin": 1,
"mushroom": 1,
"clone.hemp": 1,
"berry.blue": 1,
"berry.red": 1,
"berry.white": 1,
"berry.yellow": 1,
"berry.green": 1
}
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/farmyield <shortname> <multiplier> | Sets the multiplier for one crop shortname and saves the config. | farmingyieldmultiplier.admin |
/farmyield list | Lists every shortname with a configured multiplier. | farmingyieldmultiplier.admin |
/farmyield reload | Reloads the config file from disk so edits take effect without a reload. | farmingyieldmultiplier.admin |
| Permission | Grants |
|---|---|
farmingyieldmultiplier.admin | Grants access to the /farmyield commands. |
How the code works
Hook signature and dispatch
The plugin declares
private void OnGrowableGathered(GrowableEntity growableEntity, Item item, BasePlayer player). Oxide only calls a hook method when every argument fits the declared parameter type, so all three parameters must be the exact game types.GrowableEntityis the plant,Itemis the freshly created harvest that is about to be given to the player, andplayeris the harvester, which can be null for non-player harvesting.Reading the multiplier
config.Multipliersis aDictionary<string, float>keyed by item shortname. The hook readsitem.info.shortname, lowercases it, and looks it up. If the shortname is not present it falls back toconfig.DefaultMultiplier, so new crop types added by Facepunch are still handled. A multiplier of exactly 1.0 returns early so vanilla harvesting costs nothing in CPU.Applying and capping the amount
The new amount is
Mathf.RoundToInt(item.amount * multiplier), clamped to at least 1 and toconfig.MaxAmountPerHarvestso a high multiplier cannot produce a single stack larger than the item'sstackablevalue. The hook mutatesitem.amountin place, which is safe because the item has not been moved into a container yet; callingitem.MarkDirty()keeps any client-side preview in sync.Feedback and logging
When
config.NotifyPlayeris true and the multiplier is above 1, the player gets a shortSendReplybuilt from the lang message 'Bonus', formatted with the extra count and the crop's display name. Damage-style hooks are not involved, but the hook still fires for every wild hemp pickup, so there are noPutscalls inside it; all warnings happen at load time when a configured shortname resolves tonullviaItemManager.FindItemDefinition.Config and validation
LoadConfigreads the file and falls back toLoadDefaultConfigon any parse error, printing one warning. The defaults ship multipliers of 1.0 for corn, pumpkin, cloth, mushrooms and each berry colour so admins edit values rather than guess shortnames.SaveConfigwrites the dictionary back, preserving whatever the admin added with/farmyield.Commands and permissions
Three chat commands share the
farmingyieldmultiplier.adminpermission, registered inInit./farmyield <shortname> <multiplier>validates the shortname withItemManager.FindItemDefinition, parses the float, writes it intoconfig.Multipliers, callsSaveConfigand confirms in chat./farmyield listprints sorted entries and/farmyield reloadre-reads the file in place.
Install it on your server
Save FarmingYieldMultiplier.cs
Keep the file name exactly as the class name. Oxide matches them.Upload it to the plugins folder
Oxide:oxide/plugins/. Carbon:carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.Edit the config if you want
On first load the default config is written tooxide/config/FarmingYieldMultiplier.json(Carbon:carbon/configs/FarmingYieldMultiplier.json). After editing, reload it:oxide.reload FarmingYieldMultiplier c.reload FarmingYieldMultiplier # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default farmingyieldmultiplier.admin c.grant group default farmingyieldmultiplier.admin # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Team-owner bonus
Give a higher multiplier to players who farm inside their own building privilege.
Generate this versionGather cooldown
Rate-limit harvests to discourage macro farming.
Generate this versionWeekly rotation
Automatically rotate the boosted crop with a timer.
Generate this version
FAQ
Does this work on wild hemp and berry bushes, or only planter boxes?
Both. GrowableEntity covers planter crops and the wild plants the game spawns, so a hemp bush picked by hand fires the same hook. If you want to boost only planter crops, add a check that growableEntity.GetParentEntity() is a planter or that the growable is inside a building block.
Which shortnames do I put in the config?
Use the item shortname from the Rust item list, for example cloth, corn, pumpkin, mushroom, berry.blue, berry.red, berry.white, berry.yellow and clone.hemp for the planted hemp clone. Run /farmyield list to see the defaults, and /farmyield <shortname> 2.5 to add one.
Why does my multiplier not give exactly that number?
The result is rounded to a whole integer with Mathf.RoundToInt and clamped to the item's stack size and to your Max amount per harvest setting. A 0.4 multiplier on a 1-item harvest still yields 1, because the plugin never removes items from a player.
Can I disable the bonus chat message?
Yes, set Notify player on bonus to false in oxide/config/FarmingYieldMultiplier.json. The message text itself lives in the lang file, so you can also reword it there without touching the plugin code.
How do I install it on Oxide or Carbon?
Drop FarmingYieldMultiplier.cs into oxide/plugins, run oxide.reload FarmingYieldMultiplier from the server console, then edit oxide/config/FarmingYieldMultiplier.json and use /farmyield reload. Carbon loads the same file unchanged.
Customize it for your server
Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More gathering plugins
- Auto smelt on gatherConverts gathered metal ore into metal fragments, sulfur ore into sulfur and optionally wood into charcoal for players with a permission, usingโฆOnDispenserGatherOnDispenserBonus
- Fishing catch bonusMultiplies the amount of each caught fish and rolls a configurable chance for bonus items after every successful catch, using the OnFishCaught hook.OnFishCaught
- Furnace smelting speedSpeeds up furnaces, large furnaces, refineries and other ovens by scheduling extra smelt cycles per cook tick, scaled per oven prefab from config.OnOvenCook
Last updated Oct 26, 2026.
Customize farming yield multiplier
Customize