Skip to content
RustPluginGenerator

OnFishCatch

OnFishCatch is called by BaseFishingRod.CatchProcessBudgeted right after the rod determines what item a player caught, before the item is added to the player's inventory. Returning a different Item swaps the caught item; returning null leaves the catch untouched.

Signature
object OnFishCatch(Item item, BaseFishingRod fishingRod, BasePlayer ownerPlayer)

Parameters

Hook parameters
ParameterTypeDescription
itemItemThe Item instance that was just produced from the catch, typically the fish item created from the fishing rod's catch table. You can inspect its amount and skin, or return a different Item to substitute it.
fishingRodBaseFishingRodThe BaseFishingRod that processed the catch. Use it to read the rod's owner, position, held entity state, or to attach extra data to the catch.
ownerPlayerBasePlayerThe BasePlayer holding the rod and receiving the catch. May be null in edge cases, so null-check before using inventory or permission APIs.

Return value

Returning null (or any value that is not an Item) leaves the original catch untouched. Returning an Item instance that is not the same object as the incoming item causes the game to remove the original item and use your replacement; returning the same item instance changes nothing.

How it works

The hook fires inside BaseFishingRod.CatchProcessBudgeted, the server-side routine that decides what a player reeled in. It runs once per caught item after a CanCatchFish check has already passed, and before the game folds the item into the player's inventory. The item parameter is the raw catch item the rod produced, fishingRod is the rod entity doing the processing, and ownerPlayer is the player who owns the rod.

Declaring a narrower parameter type is rarely useful here because the arguments are already the concrete types the game passes. What matters is the return value: returning an Item that is a different instance than item makes the game remove the original and use your replacement, while any other return value (including null) keeps the original. This makes the hook a natural place for catch tables, bonus items, or per-player restrictions layered on top of Rust's built-in fish.

Useful for tracking catches for stats, awarding bonus loot for permission holders, or blocking a specific fish by returning an empty state. Keep the handler cheap — it runs on every catch, so avoid Puts here and route logging through a debug flag in config instead.

Example plugin: Bonus fish for permission holders

FishCatchBonus.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Fish Catch Bonus", "RustPluginGenerator", "1.0.0")]
    [Description("Tracks catches and swaps in a bonus fish for players with the bonus permission.")]
    public class FishCatchBonus : RustPlugin
    {
        private const string PermBonus = "fishcatchbonus.bonus";

        private class PlayerStats
        {
            [JsonProperty("Total catches")] public int Catches;
        }

        private class PluginConfig
        {
            [JsonProperty("Bonus shortname")] public string BonusShortname = "trout";
            [JsonProperty("Bonus chance (0-1)")] public float BonusChance = 0.15f;
            [JsonProperty("Milestone (multiple of catches)")] public int Milestone = 10;
        }

        private PluginConfig config;
        private Dictionary<ulong, PlayerStats> stats = new Dictionary<ulong, PlayerStats>();
        private Timer saveTimer;

        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>
            {
                ["Milestone"] = "You have caught {0} fish so far.",
                ["Bonus"] = "Lucky catch! You reeled in a bonus {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(PermBonus, this);
            stats = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, PlayerStats>>(Name)
                    ?? new Dictionary<ulong, PlayerStats>();
            saveTimer = timer.Every(300f, SaveData);
        }

        private void Unload()
        {
            saveTimer?.Destroy();
            SaveData();
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, stats);

        private object OnFishCatch(Item item, BaseFishingRod fishingRod, BasePlayer ownerPlayer)
        {
            if (item == null || ownerPlayer == null) return null;

            PlayerStats entry;
            if (!stats.TryGetValue(ownerPlayer.userID, out entry))
            {
                entry = new PlayerStats();
                stats[ownerPlayer.userID] = entry;
            }
            entry.Catches++;

            if (config.Milestone > 0 && entry.Catches % config.Milestone == 0)
                ownerPlayer.ChatMessage(Msg("Milestone", ownerPlayer.UserIDString, entry.Catches));

            if (!permission.UserHasPermission(ownerPlayer.UserIDString, PermBonus)) return null;
            if (config.BonusChance <= 0f) return null;
            if (UnityEngine.Random.Range(0f, 1f) > config.BonusChance) return null;

            Item bonus = ItemManager.CreateByName(config.BonusShortname, 1);
            if (bonus == null) return null;

            ownerPlayer.ChatMessage(Msg("Bonus", ownerPlayer.UserIDString, bonus.info.displayName.english));
            return bonus;
        }
    }
}

FishCatchBonus listens for every catch and tracks per-player counts. If the player has the <plugin>.bonus permission, has not hit the configured per-catch cap, and rolls under the configured chance, the handler builds a new Item from a configured shortname (default trout) and returns it — the game swaps the original catch for the bonus. All catches, whether swapped or not, increment the player's counter, which is echoed to chat when it hits the configured milestone. Defaults for permission, bonus shortname, chance, milestone, and messages live in the config and lang files so server owners can tune the plugin without recompiling. Counters are persisted to oxide/data/FishCatchBonus on unload and saved every five minutes by a timer that is destroyed in Unload.

Common use cases

  • Weighted fish loot table

    Roll the incoming catch against a configured weight table and return a different Item to bias results toward rarer fish or extra scrap.

  • Per-player catch stats

    Increment a counter for the owner on every catch and expose it through a chat command or persisted data file.

  • Server-wide fishing events

    During a timed event, swap the catch for a special event item so players recognise when the event is active.

Pitfalls

  • The returned Item must be the new instance

    Only a non-null Item that is a different object than the incoming item is used. Returning the same item or a clone that is not properly parented to an inventory will not behave as expected.

  • Runs on every catch

    CatchProcessBudgeted can run multiple times per cast when the rod is buffered. Do not log, save data, or spawn effects here without throttling.

  • ownerPlayer can be null

    Always null-check ownerPlayer and its UserIDString before using permissions or inventory, because stray rod state can leave the owner reference unset.

Build a plugin with OnFishCatch

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Weighted fish loot table using the OnFishCatch hook, with a config file and a permission
Generate this plugin

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 OnFishCatch

Generate