How to make a Rust recycler speed plugin (Oxide/Carbon)
Speeds up Recycler processing with a configurable seconds-per-cycle and a VIP permission for a faster rate. It uses OnRecyclerToggle to detect when a player starts a recycler and drives the recycler's own RecycleThink on a repeating timer.
Rust's recycler processes items on its own internal cadence; this plugin adds a repeating timer that calls the recycler's RecycleThink method at a configurable interval, starting when a player toggles the recycler on. It listens for OnRecyclerToggle, waits one tick so the recycler has flipped into its running state, then starts the loop. A permission route gives VIP players a faster rate. Both delays and the VIP check are config values. Timers are keyed by the recycler's network ID and destroyed in Unload, so a plugin reload does not leave orphaned loops running.
The plugin code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Recycler Speed", "RustPluginGenerator", "1.0.0")]
[Description("Speeds up recycler processing and gives VIP players a faster cycle.")]
public class RecyclerSpeed : RustPlugin
{
private const string PermVip = "recyclerspeed.vip";
private PluginConfig config;
private readonly Dictionary<ulong, Timer> activeTimers = new Dictionary<ulong, Timer>();
private class PluginConfig
{
[JsonProperty("Seconds per cycle (default)")]
public float SecondsPerCycle = 3f;
[JsonProperty("Seconds per cycle (VIP permission)")]
public float VipSecondsPerCycle = 1.5f;
[JsonProperty("Require permission for VIP speed")]
public bool RequireVipPermission = true;
}
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>
{
["SpeedInfo"] = "Recycler speed: {0}s per cycle."
}, 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(PermVip, this);
}
private object OnRecyclerToggle(Recycler recycler, BasePlayer player)
{
if (recycler == null || player == null || recycler.IsDestroyed) return null;
StopTimer(recycler);
// Hook fires before the recycler flips IsOn, so re-check next tick.
NextTick(() =>
{
if (recycler == null || recycler.IsDestroyed) return;
if (!recycler.IsOn()) return;
StartTimer(recycler, player);
});
return null;
}
private void StartTimer(Recycler recycler, BasePlayer player)
{
float interval = config.SecondsPerCycle;
bool isVip = !config.RequireVipPermission
|| (player != null && permission.UserHasPermission(player.UserIDString, PermVip));
if (isVip) interval = config.VipSecondsPerCycle;
if (interval < 0.1f) interval = 0.1f;
ulong id = recycler.net.ID.Value;
activeTimers[id] = timer.Every(interval, () =>
{
if (recycler == null || recycler.IsDestroyed || !recycler.IsOn())
{
StopTimer(recycler);
return;
}
recycler.RecycleThink();
});
}
private void StopTimer(Recycler recycler)
{
if (recycler == null) return;
ulong id = recycler.net.ID.Value;
Timer t;
if (activeTimers.TryGetValue(id, out t))
{
t.Destroy();
activeTimers.Remove(id);
}
}
private void Unload()
{
foreach (var kv in activeTimers)
{
kv.Value?.Destroy();
}
activeTimers.Clear();
}
}
}Hooks used: OnRecyclerToggle
Config file
Created automatically on first load at oxide/config/RecyclerSpeed.json.
{
"Seconds per cycle (default)": 3,
"Seconds per cycle (VIP permission)": 1.5,
"Require permission for VIP speed": true
}Commands and permissions
No chat commands. The plugin works on its own.
| Permission | Grants |
|---|---|
recyclerspeed.vip | Grants the faster VIP seconds-per-cycle rate instead of the default rate. |
How the code works
Catch the toggle with OnRecyclerToggle
object OnRecyclerToggle(Recycler recycler, BasePlayer player)fires when a player interacts with a recycler. Returnnullso vanilla behavior is not cancelled. Guardrecycler,playerandrecycler.IsDestroyedfirst, then stop any existing timer for that recycler before starting a new one.Wait one tick for the on-state
The toggle hook fires before the recycler flips
IsOn(), so checking it directly here gives the old value. UseNextTick(() => ...)to re-check after the game has applied the toggle. If the recycler is now on, callStartTimer. If it is off, the stop call you already did leaves nothing running.Pick the interval and start the loop
StartTimerchooses betweenSeconds per cycle (default)andSeconds per cycle (VIP permission)from the config. IfRequire permission for VIP speedis true, a player holdingrecyclerspeed.vipgets the VIP delay; if it is false, everyone gets the VIP delay. Values below 0.1 are raised to 0.1. Then register atimer.Every(interval, ...)callback and store it in a dictionary keyed byrecycler.net.ID.Value.Drive RecycleThink inside the timer
Inside the repeat callback, bail out if the recycler is destroyed or
!IsOn(), and callStopTimerso the loop dies when the machine stops. Otherwise callrecycler.RecycleThink()with no arguments. Because the timer fires at the configured interval, the recycler's own processing is triggered on that schedule while the plugin is active.Stop cleanly and avoid leaks
StopTimerlooks up the timer by network ID, callsDestroy()and removes the entry from the dictionary. It is safe to call when no timer exists.Unloaddestroys every timer in the map before the plugin is torn down, so callbacks do not keep ticking across a reload.Config, lang and permission setup
The nested
PluginConfiguses[JsonProperty]keys that read like plain English so admins can edit the JSON without looking at code.LoadConfigfalls back toLoadDefaultConfigwith a warning if the file is corrupt.LoadDefaultMessagesregisters a singleSpeedInfoline; there is no command that uses it yet.
Install it on your server
Save RecyclerSpeed.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/RecyclerSpeed.json(Carbon:carbon/configs/RecyclerSpeed.json). After editing, reload it:oxide.reload RecyclerSpeed c.reload RecyclerSpeed # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default recyclerspeed.vip c.grant group default recyclerspeed.vip # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Show the current rate to the player
Print the active seconds-per-cycle to the player when they open a recycler.
Generate this versionOnly speed up public recyclers
Skip the speedup when the recycler is on building privilege owned by another player.
Generate this versionPer-grade multiplier
Make higher-tier recyclers run faster by reading a per-location multiplier from config.
Generate this version
FAQ
How do I install this on Oxide or Carbon?
Drop RecyclerSpeed.cs into oxide/plugins/ on Oxide or the same folder on Carbon, then reload the plugin or restart the server. It will generate oxide/config/RecyclerSpeed.json and register the recyclerspeed.vip permission. Grant it with oxide.grant user <name> recyclerspeed.vip or to a group with oxide.group add vip followed by oxide.grant group vip recyclerspeed.vip.
Why does the recycler still feel slow sometimes?
The timer only starts on a toggle while this plugin is loaded. If a recycler was already running before the plugin loaded, the plugin's loop is not active until the next open. Turn the recycler off and on once.
Can I make VIP faster and non-VIP slower than vanilla?
Yes. Set Seconds per cycle (default) and Seconds per cycle (VIP permission) independently. Values below 0.1 are raised to 0.1 to avoid hammering the server. There is no upper clamp.
Does this work on other recycler-like entities?
No. The hook and the RecycleThink method are specific to the Recycler class. Loot barrels, quarries and ovens have different APIs and need their own plugins. A prefab that uses the Recycler component will work.
What happens if the player walks away mid-cycle?
The repeat callback checks IsOn() on every tick. When the player closes the recycler the game flips the machine off and the next tick sees that, destroys the timer and removes the entry from the internal map.
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 quality of life plugins
- BackpacksAdds a /backpack command that opens a per-player StorageContainer sized by permission, with contents persisted to oxide/data and optionally droppedโฆOnPlayerLootEndOnPlayerDisconnectedOnPlayerDeath
- Crafting speedA plugin that speeds up crafting by scaling the duration of ItemCraftTask instances when a craft starts, using a global multiplier plus optionalโฆOnItemCraft
- Day and night controlSkips or shortens night by advancing the in-game clock to a configurable morning time, and adds /day and /night admin commands.OnServerInitialized
Last updated Oct 26, 2026.
Customize recycler speed
Customize