OnRefreshVendingStock
OnRefreshVendingStock is called each time a vending machine recalculates how many units of a sell order it has in stock. It fires inside VendingMachine.RefreshSellOrderStockLevel after the machine has collected the matching items.
void OnRefreshVendingStock(VendingMachine vendingMachine, ItemDefinition itemDef)Parameters
| Parameter | Type | Description |
|---|---|---|
vendingMachine | VendingMachine | The VendingMachine whose sell order stock level is being recalculated. Use it to read the shop's sell orders, owner, position or inventory. |
itemDef | ItemDefinition | The ItemDefinition of the item whose sell order is being refreshed. Match against it to scope your logic to a specific item. |
Return value
The original method returns void and the hook's return value is ignored at the injection site. Returning a value has no effect on the stock calculation; the method always proceeds to sum the items and assign sellOrder.inStock.
How it works
The hook fires from VendingMachine.RefreshSellOrderStockLevel, the internal method a vending machine uses to recompute the inStock count of a sell order. That method runs whenever stock can change: when items are added to or removed from the machine's inventory, when the shop is configured, and on the regular refresh cycle the machine performs. The call happens after the matching items have been gathered and just before their amounts are summed and divided by the per-order quantity, so vendingMachine and itemDef describe exactly which order is being measured.
Because the hook is void and its return value is ignored, you cannot change the computed stock figure directly. Treat it as an observation point: record the item, check the machine's owner or position, or trigger your own bookkeeping. Declaring vendingMachine as a more specific type will not help since the injection passes a VendingMachine, but declaring itemDef as a narrower type such as ItemDefinition is exact. Keep the method cheap; it can run many times per machine when large inventories are edited.
Example plugin: Record sell order stock on player-owned vending machines
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Vending Stock Watcher", "RustPluginGenerator", "1.0.0")]
[Description("Records sell order stock levels on admin-owned vending machines and reports them by command.")]
public class VendingStockWatcher : RustPlugin
{
private const string PermUse = "vendingstockwatcher.use";
private class PluginConfig
{
[JsonProperty("Minimum stock warning threshold")]
public int MinimumStock { get; set; } = 5;
}
private class StockRecord
{
[JsonProperty("Item shortname")]
public string Shortname { get; set; }
[JsonProperty("Last stock")]
public int Stock { get; set; }
}
private PluginConfig config;
private Dictionary<string, Dictionary<string, StockRecord>> data = new Dictionary<string, Dictionary<string, StockRecord>>();
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 this command.",
["NoData"] = "No stock data recorded near you yet.",
["Low"] = "{0}: {1} left (below {2})",
["Ok"] = "{0}: {1} left"
}, 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);
data = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, Dictionary<string, StockRecord>>>(Name) ?? new Dictionary<string, Dictionary<string, StockRecord>>();
}
private void Unload()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
}
private void OnRefreshVendingStock(VendingMachine vendingMachine, ItemDefinition itemDef)
{
if (vendingMachine == null || itemDef == null) return;
if (!vendingMachine.OwnerID.IsSteamId()) return;
if (vendingMachine.sellOrders == null || vendingMachine.sellOrders.sellOrders == null) return;
int stock = 0;
foreach (ProtoBuf.VendingMachine.SellOrder order in vendingMachine.sellOrders.sellOrders)
{
if (order == null) continue;
if (order.itemToSellIsBP) continue;
if (order.itemToSellID != itemDef.itemid) continue;
stock = order.inStock;
break;
}
string machineKey = vendingMachine.net.ID.Value.ToString();
if (!data.TryGetValue(machineKey, out Dictionary<string, StockRecord> orders))
{
orders = new Dictionary<string, StockRecord>();
data[machineKey] = orders;
}
orders[itemDef.shortname] = new StockRecord { Shortname = itemDef.shortname, Stock = stock };
}
[ChatCommand("vending.stock")]
private void CmdVendingStock(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
VendingMachine closest = null;
float best = 10f;
foreach (BaseNetworkable entity in BaseNetworkable.serverEntities)
{
VendingMachine machine = entity as VendingMachine;
if (machine == null) continue;
float distance = Vector3.Distance(player.transform.position, machine.transform.position);
if (distance > best) continue;
best = distance;
closest = machine;
}
if (closest == null || !data.TryGetValue(closest.net.ID.Value.ToString(), out Dictionary<string, StockRecord> orders) || orders.Count == 0)
{
SendReply(player, Msg("NoData", player.UserIDString));
return;
}
foreach (StockRecord record in orders.Values)
{
if (record.Stock <= config.MinimumStock)
SendReply(player, Msg("Low", player.UserIDString, record.Shortname, record.Stock, config.MinimumStock));
else
SendReply(player, Msg("Ok", player.UserIDString, record.Shortname, record.Stock));
}
}
}
}The plugin listens for vending machine stock refreshes and, for machines owned by a player Steam ID, records the item shortname and its current stock to oxide/data. Records are stored per machine network ID, keyed by item shortname, so repeated refreshes overwrite the previous value. A chat command vending.stock finds the closest VendingMachine to the player (searching up to 10 units) and prints every recorded stock level for that machine, marking entries at or below the configured threshold with a warning. Config holds the minimum stock threshold used for that warning.
Common use cases
Out-of-stock alerts
Detect when a monitored sell order drops to zero during a refresh and notify the shop owner or staff so the machine can be restocked.
Vending analytics
Record the stock level per machine and item over time to build a dashboard of which shops sell out fastest on your server.
Verify restock scripts
Hook into the refresh cycle to confirm that a scheduled restock task actually filled the intended machine and item.
Pitfalls
Fires very often
The refresh runs on every inventory change and periodic cycle, so avoid PrintWarning, Puts or chat output here. Defer heavy work with a timer or throttle by machine ID.
Stock is not final yet
The hook fires before
inStockis written, so readingsellOrder.inStockat that moment may give the previous value. Recompute from the inventory if you need the fresh count.
Build a plugin with OnRefreshVendingStock
Describe what should happen and get a compiled plugin that uses this hook.
Edit the prompt on the next screen. 2 free plugins a month, no card.
Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.
Build a plugin with OnRefreshVendingStock
Generate