How to make a Rust fishing bonus plugin (catch multipliers)
Multiplies the amount of each caught fish and rolls a configurable chance for bonus items after every successful catch, using the OnFishCaught hook.
Vanilla fishing gives exactly one fish per successful catch, so a fishing spot produces the same yield no matter how long a player sits there. If you want fishing to compete with monuments or farming as a scrap source, you need to change what happens when the rod pulls something out.
The plugin below hooks OnFishCaught, which Oxide calls after a fishing rod resolves a catch and passes the fish's ItemDefinition, the BaseFishingRod entity and the owning BasePlayer. It rolls a multiplier: the base fish is replaced by an extra stack of the same fish item, then an independent bonus roll can add a second item pulled from a weighted list in the config. Everything is probability-driven and configurable per item, so you can tune fish yield and bonus loot separately. The config ships with a small herbs-and-scrap bonus table you can replace with your own economy items.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Fishing Bonus", "RustPluginGenerator", "1.0.0")]
[Description("Multiplies caught fish amounts and rolls a weighted bonus item table after every catch.")]
public class FishingBonus : RustPlugin
{
private const string PermAdmin = "fishingbonus.admin";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Catch multiplier")]
public float CatchMultiplier = 2f;
[JsonProperty("Bonus chance (0-1)")]
public float BonusChance = 0.25f;
[JsonProperty("Bonus items")]
public List<BonusEntry> BonusItems = new List<BonusEntry>
{
new BonusEntry { Shortname = "scrap", MinAmount = 2, MaxAmount = 8, Weight = 5 },
new BonusEntry { Shortname = "seed.hemp", MinAmount = 1, MaxAmount = 3, Weight = 3 },
new BonusEntry { Shortname = "wolfmeat.raw", MinAmount = 1, MaxAmount = 2, Weight = 1 }
};
}
private class BonusEntry
{
[JsonProperty("Item shortname")]
public string Shortname = "scrap";
[JsonProperty("Minimum amount")]
public int MinAmount = 1;
[JsonProperty("Maximum amount")]
public int MaxAmount = 1;
[JsonProperty("Weight")]
public float Weight = 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();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["NotPermitted"] = "You do not have permission to use this command.",
["Settings"] = "Fishing bonus: multiplier x{0}, bonus chance {1}%. Bonus entries: {2}.",
["Reloaded"] = "Configuration reloaded."
}, 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 OnFishCaught(ItemDefinition currentFishTarget, BaseFishingRod fishingRod, BasePlayer ownerPlayer)
{
if (currentFishTarget == null || fishingRod == null || ownerPlayer == null) return;
if (ownerPlayer.IsNpc || !ownerPlayer.IsConnected) return;
ApplyExtraFish(currentFishTarget, ownerPlayer);
if (UnityEngine.Random.Range(0f, 1f) < Mathf.Clamp01(config.BonusChance))
{
GiveBonusItem(ownerPlayer);
}
}
private void ApplyExtraFish(ItemDefinition fish, BasePlayer player)
{
int extra = Mathf.FloorToInt(Mathf.Clamp(config.CatchMultiplier, 0f, 20f));
if (extra <= 0) return;
int amount = Mathf.Min(extra, fish.stackable > 0 ? fish.stackable : extra);
for (int i = 0; i < extra; i++)
{
Item item = ItemManager.CreateByName(fish.shortname, amount, 0);
if (item == null) return;
player.GiveItem(item);
}
}
private void GiveBonusItem(BasePlayer player)
{
if (config.BonusItems == null || config.BonusItems.Count == 0) return;
float total = 0f;
foreach (BonusEntry entry in config.BonusItems)
{
if (entry == null || entry.Weight <= 0f) continue;
total += entry.Weight;
}
if (total <= 0f) return;
float roll = UnityEngine.Random.Range(0f, total);
BonusEntry chosen = null;
foreach (BonusEntry entry in config.BonusItems)
{
if (entry == null || entry.Weight <= 0f) continue;
roll -= entry.Weight;
if (roll <= 0f)
{
chosen = entry;
break;
}
}
if (chosen == null) return;
ItemDefinition def = ItemManager.FindItemDefinition(chosen.Shortname);
if (def == null) return;
int min = Mathf.Max(1, chosen.MinAmount);
int max = Mathf.Max(1, chosen.MaxAmount);
if (min > max)
{
int swap = min;
min = max;
max = swap;
}
int amount = UnityEngine.Random.Range(min, max + 1);
Item bonus = ItemManager.CreateByItemID(def.itemid, amount, 0);
if (bonus == null) return;
player.GiveItem(bonus);
}
[ChatCommand("fishbonus")]
private void CmdFishBonus(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
{
player.ChatMessage(Msg("NotPermitted", player.UserIDString));
return;
}
LoadConfig();
player.ChatMessage(Msg("Settings", player.UserIDString, config.CatchMultiplier,
Mathf.RoundToInt(config.BonusChance * 100f), config.BonusItems?.Count ?? 0));
player.ChatMessage(Msg("Reloaded", player.UserIDString));
}
private void Unload()
{
}
}
}Hooks used: OnFishCaught
Config file
Created automatically on first load at oxide/config/FishingBonus.json.
{
"Catch multiplier": 2,
"Bonus chance (0-1)": 0.25,
"Bonus items": [
{
"Item shortname": "scrap",
"Minimum amount": 2,
"Maximum amount": 8,
"Weight": 5
},
{
"Item shortname": "seed.hemp",
"Minimum amount": 1,
"Maximum amount": 3,
"Weight": 3
},
{
"Item shortname": "wolfmeat.raw",
"Minimum amount": 1,
"Maximum amount": 2,
"Weight": 1
}
]
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/fishbonus | Prints the current catch multiplier, bonus chance and reloads config from disk. | fishingbonus.admin |
| Permission | Grants |
|---|---|
fishingbonus.admin | Allows use of the /fishbonus chat command to inspect settings and reload the config. |
How the code works
Hooking the catch
OnFishCaught(ItemDefinition currentFishTarget, BaseFishingRod fishingRod, BasePlayer ownerPlayer)fires after Rust resolves which fish the rod produced. The plugin null-checks all three arguments, ignores NPC or dead owners, and readscurrentFishTarget.shortnameto build the extra stack withItemManager.CreateByName. Because the fish already exists in the player's inventory at this point, you are adding to it rather than replacing it.Applying the multiplier
CatchMultiplieris a float from theCatch multiplierconfig key. The plugin floors the value so the extra amount is a whole number of fish, then clamps it into a sane range. Each extra fish is created as its own item and handed to the player withGiveItem, which drops the item at the player's feet when the inventory is full instead of silently losing it.Rolling the bonus table
Every catch runs an independent roll against
Bonus chanceusingUnityEngine.Random.Range(0f, 1f). On success the plugin walks the configuredBonus itemslist, sums the weights, picks a random point in that total and selects the matching entry. Each entry stores a shortname and a min/max amount, so a single catch can yield one stack of scrap or a handful of berries.Guarding against bad config
Config entries with an unknown shortname are skipped:
ItemManager.CreateByNamereturns null and the plugin moves on without printing anything. TheMinimum amountandMaximum amountvalues are swapped when the minimum is higher than the maximum, so a typo in the config cannot produce an empty stack or an exception inside the hook.Chat feedback and admin reload
/fishbonusis registered with[ChatCommand]and gated behind thefishingbonus.adminpermission. It callsLoadConfigand reports the active multiplier and chance through the language file, so you can tweak values without restarting the server. All strings are registered inLoadDefaultMessages, including the message shown to players when they are not permitted.No timers, no state
The plugin holds no timers and no data files.
Unloadonly exists so the framework has a clean teardown point; nothing needs to be persisted. Because the hook is cheap and fires only on an actual catch, there is no risk of log spam or per-tick overhead on a busy fishing pier.
Install it on your server
Save FishingBonus.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/FishingBonus.json(Carbon:carbon/configs/FishingBonus.json). After editing, reload it:oxide.reload FishingBonus c.reload FishingBonus # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default fishingbonus.admin c.grant group default fishingbonus.admin # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Per-fish multipliers
Give each fish shortname its own multiplier so trout are worth more than herring.
Generate this versionBonus only for perfect catches
Award the bonus item only when the catch happens while a certain condition holds, such as holding a specific bait.
Generate this versionRarity tiers
Split the bonus table into common, rare and legendary tiers with separate chances.
Generate this version
FAQ
Does OnFishCaught fire for fish caught by traps or bots?
The hook fires for BaseFishingRod catches, which includes any rod resolved by a player. The plugin checks ownerPlayer for null and rejects NPC owners, so only real players receive the multiplier and bonus items.
Will extra fish overflow the inventory?
GiveItem places the item in the player's inventory when there is room and drops it at their position when there is not. Fish stacks are small, so overflow is rare, but nothing is deleted either way.
How do I make bonuses common but small?
Raise Bonus chance toward 1.0 and set the Minimum amount and Maximum amount of your bonus entries to 1. The weight field only controls which entry is chosen, not the size of the reward.
How do I install this on Oxide or Carbon?
Drop the compiled FishingBonus.cs into oxide/plugins on Oxide, or the same folder on a Carbon server, and the plugin loads on the next start. The config appears at oxide/config/FishingBonus.json after the first load; edit it and run /fishbonus to reload.
Can I add an item that does not exist?
Yes, but it will be skipped. The plugin validates each shortname through ItemManager.CreateByName at catch time and silently ignores entries that return null, so check your shortnames in the Rust item list before saving.
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
- Farming yield multiplierMultiplies the amount of crops a player receives when harvesting planter crops or wild plants, with a separate multiplier per item shortname.OnServerInitializedOnGrowableGathered
- 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 fishing catch bonus
Customize