Skip to content
RustPluginGenerator

How to make a Rust furnace smelting speed plugin (Oxide/Carbon)

Speeds up furnaces, large furnaces, refineries and other ovens by scheduling extra smelt cycles per cook tick, scaled per oven prefab from config. Uses the OnOvenCook hook to detect each cycle and schedules bonus work on the next tick.

Vanilla ovens process one smelt cycle per internal tick, so a large furnace can take minutes to clear a stack of ore and slow down wipe-day progress. Raising the burn rate by editing prefabs is fragile and does not survive game updates. This plugin leaves the prefabs alone and instead multiplies the work each oven does per cook tick.

The OnOvenCook hook fires whenever an oven advances a smelt cycle for an item. The plugin looks up the oven's short prefab name in the Multipliers by oven prefab dictionary, floors the value to get a whole multiplier, and subtracts one to get the number of extra cycles. If that is positive it stores the count in pendingCycles, marks the oven as processing, and schedules the work with NextTick. On the next tick it creates a temporary item with the same item id and skin, calls the OnOvenCook hook via Interface.CallHook for each extra cycle, and removes the temporary item. Ovens not listed in the dictionary fall back to Default multiplier for unlisted ovens. The electricfurnace.deployed key is included so electric furnaces get the same treatment.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("FurnaceSmeltSpeed", "RustPluginGenerator", "1.0.0")]
    [Description("Multiplies furnace and refinery smelt cycles per tick with per-oven config multipliers.")]
    public class FurnaceSmeltSpeed : RustPlugin
    {
        private const string PermUse = "furnacesmeltspeed.use";

        private PluginConfig config;
        private readonly Dictionary<ulong, int> pendingCycles = new Dictionary<ulong, int>();
        private readonly HashSet<ulong> processing = new HashSet<ulong>();

        private class PluginConfig
        {
            [JsonProperty("Multipliers by oven prefab")]
            public Dictionary<string, float> Multipliers = new Dictionary<string, float>
            {
                ["furnace"] = 4f,
                ["furnace.large"] = 2f,
                ["furnace.static"] = 1f,
                ["refinery_small_deployed"] = 2f,
                ["electricfurnace.deployed"] = 4f
            };

            [JsonProperty("Default multiplier for unlisted ovens")]
            public float DefaultMultiplier = 1f;

            [JsonProperty("Max extra cycles scheduled per cook tick")]
            public int MaxExtraCyclesPerTick = 10;
        }

        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>
            {
                ["NoConfig"] = "No oven multipliers are configured.",
                ["Line"] = "{0}: x{1}",
                ["Default"] = "default: x{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(PermUse, this);
        }

        private object OnOvenCook(BaseOven oven, Item item)
        {
            if (oven == null || item == null || item.info == null || oven.IsDestroyed) return null;
            if (!oven.IsOn()) return null;

            ulong id = oven.net.ID.Value;
            if (processing.Contains(id) || pendingCycles.ContainsKey(id)) return null;

            float multiplier = GetMultiplier(oven.ShortPrefabName);
            if (multiplier <= 1f) return null;

            int extra = (int)Math.Floor(multiplier) - 1;
            if (extra <= 0) return null;
            if (extra > config.MaxExtraCyclesPerTick) extra = config.MaxExtraCyclesPerTick;

            int itemId = item.info.itemid;
            ulong skin = item.skin;

            pendingCycles[id] = extra;
            processing.Add(id);

            NextTick(() =>
            {
                int remaining;
                if (!pendingCycles.TryGetValue(id, out remaining)) remaining = 0;
                pendingCycles.Remove(id);
                processing.Remove(id);

                if (oven == null || oven.IsDestroyed || !oven.IsOn()) return;
                if (remaining <= 0) return;

                for (int i = 0; i < remaining; i++)
                {
                    if (oven == null || oven.IsDestroyed || !oven.IsOn()) break;
                    Item fresh = ItemManager.CreateByItemID(itemId, 1, skin);
                    if (fresh == null) break;
                    processing.Add(id);
                    try
                    {
                        Interface.CallHook("OnOvenCook", oven, fresh);
                    }
                    finally
                    {
                        processing.Remove(id);
                        fresh.Remove();
                    }
                }
            });

            return null;
        }

        private float GetMultiplier(string shortPrefabName)
        {
            if (string.IsNullOrEmpty(shortPrefabName)) return config.DefaultMultiplier;
            string key = shortPrefabName.ToLowerInvariant();

            foreach (var entry in config.Multipliers)
            {
                if (entry.Key != null && entry.Key.ToLowerInvariant() == key)
                    return entry.Value;
            }

            return config.DefaultMultiplier;
        }

        [ChatCommand("smeltspeed")]
        private void CmdSmeltSpeed(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage("You do not have permission to use this command.");
                return;
            }

            if (config.Multipliers.Count == 0)
            {
                player.ChatMessage(Msg("NoConfig", player.UserIDString));
                return;
            }

            foreach (var entry in config.Multipliers)
                player.ChatMessage(Msg("Line", player.UserIDString, entry.Key, entry.Value));

            player.ChatMessage(Msg("Default", player.UserIDString, config.DefaultMultiplier));
        }

        private void Unload()
        {
            pendingCycles.Clear();
            processing.Clear();
        }
    }
}

Hooks used: OnOvenCook

Config file

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

FurnaceSmeltSpeed.json
{
  "Multipliers by oven prefab": {
    "furnace": 4,
    "furnace.large": 2,
    "furnace.static": 1,
    "refinery_small_deployed": 2,
    "electricfurnace.deployed": 4
  },
  "Default multiplier for unlisted ovens": 1,
  "Max extra cycles scheduled per cook tick": 10
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/smeltspeedPrints the configured multiplier for each oven prefab and the default fallback to the player's chat.furnacesmeltspeed.use
Permissions
PermissionGrants
furnacesmeltspeed.useGrants access to the smeltspeed chat command.

How the code works

  1. Map oven prefabs to multipliers

    The PluginConfig class holds Multipliers by oven prefab as a dictionary of short prefab name to float, plus Default multiplier for unlisted ovens and Max extra cycles scheduled per cook tick. Keys are compared case-insensitively by GetMultiplier, which lowercases the oven's ShortPrefabName and walks the dictionary. Any oven name not in the dictionary returns DefaultMultiplier. LoadConfig reads the file and falls back to LoadDefaultConfig if it cannot be parsed.

  2. Intercept each cook cycle in OnOvenCook

    object OnOvenCook(BaseOven oven, Item item) fires when an oven advances an item's smelt timer. The method bails out if the oven or item is null, if item.info is null, if oven.IsDestroyed, or if !oven.IsOn(). It also returns early if the entity id is already in processing or pendingCycles, which prevents its own extra cycles from re-entering. The hook always returns null so the game runs its own normal cycle.

  3. Calculate the extra cycles

    GetMultiplier(oven.ShortPrefabName) returns the configured value. If it is 1 or below there is nothing to do. Otherwise extra is (int)Math.Floor(multiplier) - 1, so a multiplier of 4 means three bonus cycles on top of vanilla. extra is capped at Max extra cycles scheduled per cook tick so a very high value cannot flood the server on a full ore stack. When extra is above zero the count is stored in pendingCycles and the entity id is added to processing.

  4. Run the extra cycles on the next tick

    NextTick removes the entity id from pendingCycles and processing, then re-checks the oven. For each remaining cycle it creates a one-unit item with ItemManager.CreateByItemID(item.info.itemid, 1, item.skin), adds the entity id back to processing, and calls the OnOvenCook hook via Interface.CallHook. The temporary item is removed after the call. Destroyed or switched-off ovens break out of the loop early.

  5. Report the active multipliers

    The [ChatCommand("smeltspeed")] handler checks permission.UserHasPermission against furnacesmeltspeed.use, which is registered in Init. It prints one line per dictionary entry using the Line lang key, then the default with Default. If the dictionary is empty an admin sees the NoConfig message. No console command is registered.

  6. Reload and unload cleanly

    Every multiplier is read from config on each hook call, so edits take effect on the next cook tick after oxide.reload FurnaceSmeltSpeed. Unload clears both pendingCycles and processing so stale entity ids never survive a reload. The plugin creates no timers and stores no data files, and it never touches the oven's item container.

Install it on your server

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

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

Variations

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

  • Global multiplier only

    Drop per-prefab keys and use one number for every oven on the server.

    Generate this version
  • Permission-gated boost

    Only ovens owned by players with a specific permission smelt faster.

    Generate this version
  • Bonus output instead of speed

    Keep vanilla timing but award extra smelted items on each cycle.

    Generate this version

FAQ

How do I install the plugin?

Put the .cs file in oxide/plugins on an Oxide server, or in the same folder with Carbon since Carbon loads Oxide plugins. Run oxide.reload FurnaceSmeltSpeed and a default config appears in oxide/config. No client download is required.

Does the plugin burn fuel faster?

The plugin never reads or changes the oven's fuel state; it only adds extra cook cycles. Because ore clears faster, an oven stops cooking sooner in real time, which is what admins usually expect when they raise a multiplier.

What prefab names do I put in the config?

Use the oven's short prefab name, for example furnace, furnace.large, refinery_small_deployed or electricfurnace.deployed. Keys are matched case-insensitively, so casing does not matter. Ovens not listed use Default multiplier for unlisted ovens.

How does the multiplier map to actual speed?

The plugin floors the multiplier and subtracts one. A value of 4 schedules three extra cycles alongside the vanilla cycle; a value of 1.5 or 1 does nothing because the floored value minus one is zero or less. Only whole numbers give a speed-up.

Can a very high multiplier lag the server?

Every extra cycle costs a small amount of server work per oven per tick. The plugin clamps the number of extra cycles with Max extra cycles scheduled per cook tick, default 10, but with hundreds of active furnaces and high multipliers expect higher CPU usage. Test on your own hardware first.

Customize it for your server

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

Prompt: Write an Oxide plugin called FurnaceSmeltSpeed that speeds up furnace smelting using OnOvenCook, with per-oven-prefab multipliers in config defaulting to furnace 4, furnace.large 2 and everything else 1, plus a /smeltspeed command behind a permission.
Customize for my server

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

Last updated Oct 26, 2026.

Customize furnace smelting speed

Customize