Skip to content
RustPluginGenerator

How to make a Rust gather bonus plugin (VIP tiers)

Gives players with tiered permissions extra resources when they gather from nodes with a ResourceDispenser or pick up collectible entities, using OnDispenserGather and OnCollectiblePickup.

Vanilla Rust pays the same amount per swing to everyone, so a VIP gather bonus needs to be applied after the game has already decided the yield. The two hooks that matter are OnDispenserGather, which fires for each item a player pulls from a tree, ore node or other ResourceDispenser, and OnCollectiblePickup, which fires when a player collects a hemp bush, mushroom or other CollectibleEntity. Both receive the finished Item, so raising item.amount before the pickup is finalized is enough. The plugin below reads a per-tier multiplier from its config, picks the best tier the player holds from a configurable permission list, scales the gathered or collected amount, and awards the difference to the player. A global multiplier lets admins run a short double-gather event, and a chat command shows a player their current multiplier.

The plugin code

VipGatherBonus.cs
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("Vip Gather Bonus", "RustPluginGenerator", "1.0.0")]
    [Description("Gives permission-tiered players extra resources when gathering and picking up collectibles.")]
    public class VipGatherBonus : RustPlugin
    {
        private const string PermUse = "vipgatherbonus.use";

        private PluginConfig config;
        private readonly Dictionary<string, float> tierCache = new Dictionary<string, float>();
        private Timer refreshTimer;

        private class Tier
        {
            [JsonProperty("Permission suffix")]
            public string Permission { get; set; } = "vip1";

            [JsonProperty("Multiplier")]
            public float Multiplier { get; set; } = 1.25f;
        }

        private class PluginConfig
        {
            [JsonProperty("Global bonus multiplier")]
            public float GlobalBonus { get; set; } = 1f;

            [JsonProperty("Top multiplier only")]
            public bool TopMultiplierOnly { get; set; } = true;

            [JsonProperty("Tier permissions")]
            public List<Tier> Tiers { get; set; } = new List<Tier>
            {
                new Tier { Permission = "vip1", Multiplier = 1.25f },
                new Tier { Permission = "vip2", Multiplier = 1.5f },
                new Tier { Permission = "vip3", Multiplier = 2f }
            };
        }

        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>
            {
                ["NoBonus"] = "You have no gather bonus. Ask an admin about VIP tiers.",
                ["Bonus"] = "Your gather bonus is x{0} (tier: {1}).",
                ["Global"] = "Your gather bonus is x{0}.",
                ["NoPermission"] = "You do not have permission to use this command."
            }, 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);
            if (config.Tiers != null)
            {
                foreach (var tier in config.Tiers)
                {
                    if (tier == null || string.IsNullOrEmpty(tier.Permission)) continue;
                    permission.RegisterPermission(Prefix + "." + tier.Permission, this);
                }
            }
        }

        private void OnServerInitialized()
        {
            RefreshCache();
            refreshTimer = timer.Every(60f, RefreshCache);
        }

        private void Unload()
        {
            refreshTimer?.Destroy();
            tierCache.Clear();
        }

        private string Prefix => Name.ToLower().Replace(" ", "");

        private void RefreshCache()
        {
            tierCache.Clear();
            foreach (var player in BasePlayer.activePlayerList)
            {
                if (player == null || player.IsNpc) continue;
                tierCache[player.UserIDString] = ResolveMultiplier(player.UserIDString);
            }
        }

        private float ResolveMultiplier(string userId)
        {
            float result = 1f;
            float top = 1f;
            if (config.Tiers != null)
            {
                foreach (var tier in config.Tiers)
                {
                    if (tier == null || tier.Multiplier <= 1f) continue;
                    if (!permission.UserHasPermission(userId, Prefix + "." + tier.Permission)) continue;
                    if (tier.Multiplier > top) top = tier.Multiplier;
                    if (!config.TopMultiplierOnly) result *= tier.Multiplier;
                }
            }
            if (config.TopMultiplierOnly) result = top;
            return result * Math.Max(1f, config.GlobalBonus);
        }

        private float GetMultiplier(BasePlayer player)
        {
            if (player == null) return 1f;
            float cached;
            if (tierCache.TryGetValue(player.UserIDString, out cached)) return cached;
            float value = ResolveMultiplier(player.UserIDString);
            tierCache[player.UserIDString] = value;
            return value;
        }

        private int ScaleAmount(Item item, float multiplier)
        {
            if (item == null || item.info == null) return 0;
            int scaled = (int)Math.Ceiling(item.amount * multiplier);
            if (item.info.stackable > 0 && scaled > item.info.stackable) scaled = item.info.stackable;
            return scaled < item.amount ? item.amount : scaled;
        }

        private object OnDispenserGather(ResourceDispenser resourceDispenser, BasePlayer entity, Item item)
        {
            if (entity == null || entity.IsNpc || item == null) return null;
            float multiplier = GetMultiplier(entity);
            if (multiplier <= 1f) return null;
            item.amount = ScaleAmount(item, multiplier);
            return null;
        }

        private object OnCollectiblePickup(CollectibleEntity collectibleEntity, BasePlayer reciever, bool eat)
        {
            if (reciever == null || reciever.IsNpc || eat) return null;
            float multiplier = GetMultiplier(reciever);
            if (multiplier <= 1f) return null;
            var item = reciever.GetActiveItem();
            if (item == null) return null;
            item.amount = ScaleAmount(item, multiplier);
            return null;
        }

        [ChatCommand("gatherbonus")]
        private void CmdGatherBonus(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }
            float multiplier = GetMultiplier(player);
            string tierName = "none";
            if (config.Tiers != null)
            {
                foreach (var tier in config.Tiers)
                {
                    if (tier == null) continue;
                    if (permission.UserHasPermission(player.UserIDString, Prefix + "." + tier.Permission))
                    {
                        tierName = tier.Permission;
                        if (config.TopMultiplierOnly) break;
                    }
                }
            }
            if (multiplier <= 1f) player.ChatMessage(Msg("NoBonus", player.UserIDString));
            else if (tierName == "none") player.ChatMessage(Msg("Global", player.UserIDString, multiplier));
            else player.ChatMessage(Msg("Bonus", player.UserIDString, multiplier, tierName));
        }
    }
}

Hooks used: OnServerInitialized, OnDispenserGather, OnCollectiblePickup

Config file

Created automatically on first load at oxide/config/VipGatherBonus.json.

VipGatherBonus.json
{
  "Global bonus multiplier": 1,
  "Top multiplier only": true,
  "Tier permissions": [
    {
      "Permission suffix": "vip1",
      "Multiplier": 1.25
    },
    {
      "Permission suffix": "vip2",
      "Multiplier": 1.5
    },
    {
      "Permission suffix": "vip3",
      "Multiplier": 2
    }
  ]
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/gatherbonusShow your current gather tier and bonus multiplier.vipgatherbonus.use
Permissions
PermissionGrants
vipgatherbonus.useAllows using the /gatherbonus command.
vipgatherbonus.vip1Tier 1 gather bonus, set by the Tier 1 multiplier in the config.
vipgatherbonus.vip2Tier 2 gather bonus, set by the Tier 2 multiplier in the config.
vipgatherbonus.vip3Tier 3 gather bonus, set by the Tier 3 multiplier in the config.

How the code works

  1. Model the tiers in the config

    The Tier permissions key is a list of objects, each with a Permission suffix (vip1, vip2, vip3) and a Multiplier. The plugin builds the full node from its own name: Prefix is the class name lowercased with spaces removed, so the full permission is vipgatherbonus.vip1 and so on. List them lowest to highest. Top multiplier only decides whether holding several tiers gives the best one (default) or multiplies them together, and Global bonus multiplier is applied to the final result so admins can run a temporary double-gather event.

  2. Register permissions at startup

    Init registers vipgatherbonus.use and then walks config.Tiers, registering Prefix + "." + tier.Permission for each. Registering here means the nodes exist in the permissions UI before anyone is granted them. If you edit the tier list in the config and reload the plugin, the new nodes are registered on the next reload.

  3. Pick the best tier for a player

    ResolveMultiplier(string userId) starts at 1, walks the tier list with permission.UserHasPermission, and either keeps the highest multiplier or multiplies them when Top multiplier only is false. The result is multiplied by Global bonus multiplier. GetMultiplier(BasePlayer) reads a dictionary keyed on UserIDString; the dictionary is filled by RefreshCache, which runs on OnServerInitialized and then every 60 seconds through a timer.Every, so a revoked permission takes effect within a minute without a permission check on every swing.

  4. Scale the swing yield in OnDispenserGather

    The hook runs for every item a ResourceDispenser produces. The plugin null-checks the dispenser and player, ignores NPC gatherers via IsNpc, gets the multiplier and, if it is above 1, sets item.amount through ScaleAmount. That helper rounds up, caps at item.info.stackable, and never lowers the amount below what the game produced. Returning null keeps Oxide's normal handling, which grants the item to the player.

  5. Scale collectibles in OnCollectiblePickup

    CollectibleEntity pickups (hemp, berries, mushrooms) go through a different hook and pass an eat flag. When the player is eating the collectible in the field the item is consumed immediately, so the plugin returns early for eat == true and only adjusts item.amount when a real item is going into the inventory. It reads that item from reciever.GetActiveItem() and reuses ScaleAmount so both paths round the same way.

  6. Report and clean up

    /gatherbonus is guarded by vipgatherbonus.use, resolves the multiplier with GetMultiplier, finds the highest tier the player holds, and prints one of three messages through lang: no bonus, global-only bonus, or a tier bonus with both the multiplier and the tier suffix. On Unload the refresh timer is destroyed and the dictionary cleared. No data is written to disk; tier state always comes from the permission system.

Install it on your server

  1. Save VipGatherBonus.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. 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.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/VipGatherBonus.json (Carbon: carbon/configs/VipGatherBonus.json). After editing, reload it:oxide.reload VipGatherBonus c.reload VipGatherBonus # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default vipgatherbonus.use c.grant group default vipgatherbonus.use # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

FAQ

Does the bonus apply to the tool decay or durability?

No. OnDispenserGather only changes the produced Item. Damage to the tool is handled elsewhere, so a tier 3 player's pickaxe wears out at the same rate as everyone else's.

Why do I only see whole numbers?

item.amount is an integer. ScaleAmount uses Math.Ceiling, so a 1.25x multiplier on a 10 yield gives 13. A yield of 1 becomes 2 with any multiplier above 1, which is why Top multiplier only exists for stacked tiers.

Do bonuses stack by default?

No. With Top multiplier only at true the plugin uses the highest tier the player holds. Set it to false to multiply every held tier together, and raise Global bonus multiplier for events instead.

Does it work on Carbon?

Yes. Carbon loads the same Oxide plugin file. Drop VipGatherBonus.cs into oxide/plugins and grant the vipgatherbonus.vip1 style permissions with your permissions plugin or oxide.grant.

Why didn't my permission take effect immediately?

Tier lookups are cached and refreshed every 60 seconds to keep the gather hooks cheap. Reload the plugin (oxide.reload VipGatherBonus) or wait for the refresh after granting a permission.

Customize it for your server

Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.

Prompt: Create a Rust Oxide plugin called VipGatherBonus that gives vip1/vip2/vip3 permission holders 1.25x/1.5x/2x gather yield via OnDispenserGather and OnCollectiblePickup, with a /gatherbonus command and global bonus config.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize vip gather bonus

Customize