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
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.
{
"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
| Command | What it does | Permission |
|---|---|---|
/smeltspeed | Prints the configured multiplier for each oven prefab and the default fallback to the player's chat. | furnacesmeltspeed.use |
| Permission | Grants |
|---|---|
furnacesmeltspeed.use | Grants access to the smeltspeed chat command. |
How the code works
Map oven prefabs to multipliers
The
PluginConfigclass holdsMultipliers by oven prefabas a dictionary of short prefab name tofloat, plusDefault multiplier for unlisted ovensandMax extra cycles scheduled per cook tick. Keys are compared case-insensitively byGetMultiplier, which lowercases the oven'sShortPrefabNameand walks the dictionary. Any oven name not in the dictionary returnsDefaultMultiplier.LoadConfigreads the file and falls back toLoadDefaultConfigif it cannot be parsed.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, ifitem.infois null, ifoven.IsDestroyed, or if!oven.IsOn(). It also returns early if the entity id is already inprocessingorpendingCycles, which prevents its own extra cycles from re-entering. The hook always returnsnullso the game runs its own normal cycle.Calculate the extra cycles
GetMultiplier(oven.ShortPrefabName)returns the configured value. If it is 1 or below there is nothing to do. Otherwiseextrais(int)Math.Floor(multiplier) - 1, so a multiplier of 4 means three bonus cycles on top of vanilla.extrais capped atMax extra cycles scheduled per cook tickso a very high value cannot flood the server on a full ore stack. Whenextrais above zero the count is stored inpendingCyclesand the entity id is added toprocessing.Run the extra cycles on the next tick
NextTickremoves the entity id frompendingCyclesandprocessing, then re-checks the oven. For each remaining cycle it creates a one-unit item withItemManager.CreateByItemID(item.info.itemid, 1, item.skin), adds the entity id back toprocessing, and calls theOnOvenCookhook viaInterface.CallHook. The temporary item is removed after the call. Destroyed or switched-off ovens break out of the loop early.Report the active multipliers
The
[ChatCommand("smeltspeed")]handler checkspermission.UserHasPermissionagainstfurnacesmeltspeed.use, which is registered inInit. It prints one line per dictionary entry using theLinelang key, then the default withDefault. If the dictionary is empty an admin sees theNoConfigmessage. No console command is registered.Reload and unload cleanly
Every multiplier is read from
configon each hook call, so edits take effect on the next cook tick afteroxide.reload FurnaceSmeltSpeed.Unloadclears bothpendingCyclesandprocessingso 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
Save FurnaceSmeltSpeed.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/FurnaceSmeltSpeed.json(Carbon:carbon/configs/FurnaceSmeltSpeed.json). After editing, reload it:oxide.reload FurnaceSmeltSpeed c.reload FurnaceSmeltSpeed # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip: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 versionPermission-gated boost
Only ovens owned by players with a specific permission smelt faster.
Generate this versionBonus 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.
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
- Fishing catch bonusMultiplies the amount of each caught fish and rolls a configurable chance for bonus items after every successful catch, using the OnFishCaught hook.OnFishCaught
Last updated Oct 26, 2026.
Customize furnace smelting speed
Customize