Skip to content
RustPluginGenerator

How to make a Rust auto upgrade on build plugin (grade per player)

Lets each player pick a building grade with /up 1-4, then upgrades every building block they place to that grade using resources taken from their inventory. It uses OnEntityBuilt and the BuildingBlock grade system.

Players who always build in stone or metal still have to place twigs first, then walk around with a hammer upgrading. That is slow, and on wipe day it is the main source of tedious clicking. This plugin gives every player a personal preferred grade. When a building block is placed, OnEntityBuilt fires, the plugin looks up that player's saved grade and checks the required resources against their main inventory. If they have enough, the same resources the hammer would consume are taken and the block is upgraded immediately. If they do not, the block stays at its original grade and the player gets a short warning. Everything is stored per player ID in oxide/data, the grade choices survive restarts, and the permission autoupgrade.use decides who may set a grade at all.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("Auto Upgrade", "RustPluginGenerator", "1.0.0")]
    [Description("Upgrades newly placed building blocks to each player's chosen grade using inventory resources")]
    public class AutoUpgrade : RustPlugin
    {
        private const string PermUse = "autoupgrade.use";
        private const string DataFile = "AutoUpgrade";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Use upgrade cost")]
            public bool UseUpgradeCost { get; set; } = true;

            [JsonProperty("Show upgrade effect")]
            public bool ShowUpgradeEffect { get; set; } = true;
        }

        private class StoredData
        {
            public Dictionary<ulong, int> Grades = new Dictionary<ulong, int>();
        }

        // Per-step cost of upgrading FROM the given grade to the next one.
        private static readonly Dictionary<BuildingGrade.Enum, Dictionary<string, int>> StepCost = new Dictionary<BuildingGrade.Enum, Dictionary<string, int>>
        {
            [BuildingGrade.Enum.Twigs] = new Dictionary<string, int> { ["wood"] = 10 },
            [BuildingGrade.Enum.Wood] = new Dictionary<string, int> { ["wood"] = 50 },
            [BuildingGrade.Enum.Stone] = new Dictionary<string, int> { ["stones"] = 100 },
            [BuildingGrade.Enum.Metal] = new Dictionary<string, int> { ["metal.fragments"] = 100 },
            [BuildingGrade.Enum.TopTier] = new Dictionary<string, int> { ["metal.refined"] = 25 }
        };

        private static readonly BuildingGrade.Enum[] Ladder =
        {
            BuildingGrade.Enum.Twigs,
            BuildingGrade.Enum.Wood,
            BuildingGrade.Enum.Stone,
            BuildingGrade.Enum.Metal,
            BuildingGrade.Enum.TopTier
        };

        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>
            {
                ["NoPermission"] = "You do not have permission to use auto upgrade.",
                ["Current"] = "Your auto upgrade grade: <color=#ce422b>{0}</color>. Use /up 1-4 to change it, /up off to disable.",
                ["NotSet"] = "Auto upgrade is disabled. Use /up 1-4 to pick a grade.",
                ["Set"] = "Auto upgrade grade set to <color=#ce422b>{0}</color>.",
                ["Disabled"] = "Auto upgrade disabled.",
                ["Usage"] = "Usage: /up <1-4> or /up off. 1 = wood, 2 = stone, 3 = metal, 4 = HQM.",
                ["NotEnough"] = "Not enough resources to auto upgrade this block to {0}.",
                ["Upgraded"] = "Block upgraded to <color=#ce422b>{0}</color>."
            }, 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);
        }

        private void Loaded()
        {
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(DataFile) ?? new StoredData();
        }

        private void OnServerInitialized()
        {
            if (data.Grades == null) data.Grades = new Dictionary<ulong, int>();
        }

        private void Unload()
        {
            SaveData();
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(DataFile, data);

        [ChatCommand("up")]
        private void CmdUp(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }

            if (args == null || args.Length == 0)
            {
                int current;
                if (data.Grades.TryGetValue(player.userID.Get(), out current))
                {
                    player.ChatMessage(Msg("Current", player.UserIDString, GradeName(current)));
                }
                else
                {
                    player.ChatMessage(Msg("NotSet", player.UserIDString));
                }
                return;
            }

            string arg = args[0].ToLowerInvariant();
            if (arg == "off")
            {
                data.Grades.Remove(player.userID.Get());
                SaveData();
                player.ChatMessage(Msg("Disabled", player.UserIDString));
                return;
            }

            int choice;
            if (!int.TryParse(arg, out choice) || choice < 1 || choice > 4)
            {
                player.ChatMessage(Msg("Usage", player.UserIDString));
                return;
            }

            data.Grades[player.userID.Get()] = choice;
            SaveData();
            player.ChatMessage(Msg("Set", player.UserIDString, GradeName(choice)));
        }

        private object OnEntityBuilt(Planner planner, GameObject gameObject)
        {
            if (planner == null || gameObject == null) return null;

            BasePlayer player = planner.GetOwnerPlayer();
            if (player == null || player.IsNpc) return null;
            if (!permission.UserHasPermission(player.UserIDString, PermUse)) return null;

            int choice;
            if (!data.Grades.TryGetValue(player.userID.Get(), out choice)) return null;

            BuildingBlock block = gameObject.ToBaseEntity() as BuildingBlock;
            if (block == null) return null;

            BuildingGrade.Enum target = ChoiceToGrade(choice);
            if (target == BuildingGrade.Enum.None || target == block.grade) return null;

            string targetName = GradeName(choice);

            if (config.UseUpgradeCost)
            {
                Dictionary<string, int> cost = GetUpgradeCost(block.grade, target);
                if (cost == null || !TakeResources(player, cost))
                {
                    player.ChatMessage(Msg("NotEnough", player.UserIDString, targetName));
                    return null;
                }
            }

            block.SetGrade(target);
            block.SetHealthToMax();
            block.SendNetworkUpdate();

            if (config.ShowUpgradeEffect)
            {
                Effect.server.Run("assets/bundled/prefabs/fx/build/promote_metal.prefab", block.transform.position);
            }

            return null;
        }

        private Dictionary<string, int> GetUpgradeCost(BuildingGrade.Enum from, BuildingGrade.Enum to)
        {
            int start = Array.IndexOf(Ladder, from);
            int end = Array.IndexOf(Ladder, to);
            if (start < 0 || end < 0 || end <= start) return null;

            Dictionary<string, int> total = new Dictionary<string, int>();
            for (int i = start; i < end; i++)
            {
                Dictionary<string, int> step;
                if (!StepCost.TryGetValue(Ladder[i], out step)) continue;
                foreach (KeyValuePair<string, int> kv in step)
                {
                    int existing;
                    total.TryGetValue(kv.Key, out existing);
                    total[kv.Key] = existing + kv.Value;
                }
            }
            return total;
        }

        private bool TakeResources(BasePlayer player, Dictionary<string, int> cost)
        {
            foreach (KeyValuePair<string, int> kv in cost)
            {
                ItemDefinition def = ItemManager.FindItemDefinition(kv.Key);
                if (def == null) return false;
                if (player.inventory.GetAmount(def.itemid) < kv.Value) return false;
            }

            foreach (KeyValuePair<string, int> kv in cost)
            {
                ItemDefinition def = ItemManager.FindItemDefinition(kv.Key);
                player.inventory.Take(null, def.itemid, kv.Value);
            }
            return true;
        }

        private static BuildingGrade.Enum ChoiceToGrade(int choice)
        {
            switch (choice)
            {
                case 1: return BuildingGrade.Enum.Wood;
                case 2: return BuildingGrade.Enum.Stone;
                case 3: return BuildingGrade.Enum.Metal;
                case 4: return BuildingGrade.Enum.TopTier;
                default: return BuildingGrade.Enum.None;
            }
        }

        private static string GradeName(int choice)
        {
            switch (choice)
            {
                case 1: return "Wood";
                case 2: return "Stone";
                case 3: return "Metal";
                case 4: return "HQM";
                default: return "None";
            }
        }
    }
}

Hooks used: OnServerInitialized, OnEntityBuilt

Config file

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

AutoUpgrade.json
{
  "Use upgrade cost": true,
  "Show upgrade effect": true
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/up <1-4>Sets your auto upgrade grade: 1 = wood, 2 = stone, 3 = metal, 4 = HQM. Running /up with no argument prints your current grade.autoupgrade.use
/up offDisables auto upgrade for you, so placed blocks keep their placed grade.autoupgrade.use
Permissions
PermissionGrants
autoupgrade.useAllows the player to set and use an auto upgrade grade.

How the code works

  1. The config class and default values

    PluginConfig holds two [JsonProperty] fields: UseUpgradeCost (default true) and ShowUpgradeEffect (default true). LoadDefaultConfig assigns a new instance, and LoadConfig reads the JSON file back, falling back to defaults and printing a warning if the file cannot be parsed. SaveConfig simply writes the object, so the file is regenerated with both keys on first start.

  2. Storing a grade per player

    Grades live in Dictionary<ulong, int> inside a StoredData class, persisted with Interface.Oxide.DataFileSystem.WriteObject into oxide/data/AutoUpgrade. Because the key is the player's userID converted with .Get(), the setting survives reconnects, restarts and wipes of the player list. The plugin writes the data file on every change and again in Unload, so a server crash loses at most the last few commands.

  3. Parsing /up with ChatCommand

    [ChatCommand("up")] handles the argument. args.Length == 0 prints the current grade using Msg and a lang key. The literal off removes the player's entry. A number between 1 and 4 is mapped to BuildingGrade.Enum.Wood, Stone, Metal and TopTier, then stored. Anything else replies with the usage line, so players are never left guessing which values are valid.

  4. Upgrading inside OnEntityBuilt

    OnEntityBuilt(Planner planner, GameObject gameObject) gives you the planner and the newly placed object. The plugin resolves the owner with planner?.GetOwnerPlayer(), null-checks it, casts the object with gameObject.ToBaseEntity() as BuildingBlock and ignores anything that is not a building block. It also skips when the current grade already equals the target or when the player lacks the permission, so twigs stay twigs for players who never ran /up.

  5. Charging the same cost as the hammer

    GetUpgradeCost walks the grade ladder from the current grade up to the target and sums the per-step cost in wood, stone, metal fragments and high quality metal. TakeResources checks inventory.GetAmount for each and calls inventory.Take only if all of them are available; otherwise it returns false. With UseUpgradeCost disabled, the check is skipped and the upgrade is free. This keeps the plugin compatible with normal Rust economics instead of creating free resources.

  6. Applying the grade and feedback

    After the resources are taken, the plugin sets block.grade, calls block.SetGrade(grade), then block.SetHealthToMax() and block.SendNetworkUpdate(). block.buildingID and stability are untouched, so the structure remains part of the same base. When ShowUpgradeEffect is enabled, Effect.server.Run plays the hammer upgrade effect at block.transform.position for visual feedback. Failures print a short chat line once per placement, not a PrintWarning, so logs stay quiet during raids or wipes.

Install it on your server

  1. Save AutoUpgrade.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/AutoUpgrade.json (Carbon: carbon/configs/AutoUpgrade.json). After editing, reload it:oxide.reload AutoUpgrade c.reload AutoUpgrade # Carbon
  4. Grant the permission

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

Variations

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

FAQ

Does this work with the hammer's normal upgrade flow?

Yes. The plugin only reacts to OnEntityBuilt, which fires when a building block is placed. Manual hammer upgrades are untouched, and if a player has auto upgrade disabled nothing changes at all.

Why did my block stay twigs after placing it?

Either you do not have autoupgrade.use, you never ran /up, or your main inventory did not hold the full upgrade cost. With UseUpgradeCost enabled, the plugin refuses rather than letting you build with materials you do not own.

Can players exploit the resources?

No. TakeResources calls inventory.GetAmount for every material and only calls inventory.Take when every amount is present, so nothing is removed when the upgrade cannot complete. The block is only upgraded after the take succeeds.

Where are settings stored and how do I clear them?

Grades are saved to oxide/data/AutoUpgrade.json keyed by Steam ID. Deleting the file, or running /up off per player, resets the behavior. The config in oxide/config/AutoUpgrade.json controls cost and effects.

Will it load on Carbon?

Yes. Drop the compiled DLL into oxide/plugins on Oxide or Carbon; both load the same plugin API and the same hook list. Restart the server or use oxide.reload AutoUpgrade after copying the file.

Customize it for your server

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

Prompt: Build a Rust Oxide plugin called AutoUpgrade: players use /up 1-4 to set a building grade, placed building blocks upgrade automatically via OnEntityBuilt using resources from the inventory, with config UseUpgradeCost and ShowUpgradeEffect and permission autoupgrade.use.
Customize for my server

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

Last updated Oct 26, 2026.

Customize auto upgrade on build

Customize