Skip to content
RustPluginGenerator

OnBuyVendingItem

OnBuyVendingItem is called when a player confirms a purchase on a VendingMachine, before the server moves currency and sold items. Returning a non-null value aborts the purchase.

Signature
object OnBuyVendingItem(VendingMachine vendingMachine, BasePlayer player, int orderIndex, int amount)

Parameters

Hook parameters
ParameterTypeDescription
vendingMachineVendingMachineThe VendingMachine entity the player is buying from. Use it to read sellOrders, the machine's position or OwnerID to apply per-machine rules.
playerBasePlayerThe BasePlayer who triggered the purchase RPC. This is the buyer, not necessarily the machine owner.
orderIndexintIndex of the sell order the player clicked in the shop UI (num in the decompiled method). Use it to look up vendingMachine.sellOrders.sellOrders[orderIndex].
amountintNumber of items the player requested in the purchase RPC (num2 in the decompiled method). This value comes from the client and is validated by the game afterwards.

Return value

Return null to let the purchase proceed normally. Returning any non-null value (commonly true) makes BuyItem return immediately, so no currency is taken and no items are handed out.

How it works

The hook fires inside VendingMachine.BuyItem, after the vending machine has checked that it is not busy and after the wait-for-vending toast, but before the sell orders are iterated and stock is consumed. Any RPC that reaches BuyItem triggers it: a normal buy from the shop UI and the matching buy path used by vending machines. vendingMachine gives you the machine, its sellOrders list and OwnerID; player is the buyer; the order index and requested amount let you resolve which ProtoBuf.VendingMachine.SellOrder is being bought and check currencyItem.amount * amount type math before the game does. Because a non-null return stops BuyItem before it touches inventory, this is the place to refuse purchases (blocked shop, blacklisted item, insufficient custom currency, event restriction) without the player losing anything. Declaring vendingMachine as a more specific type does not help here since the game always passes a VendingMachine; narrowing only makes sense if you plan to extend the hook to vendors of a subclass. Prefer CanPurchaseItem for per-item checks if you only need a yes/no gate, since it fires earlier in the flow.

Example plugin: Restrict vending machine sales to a permission group

VendingPurchaseGate.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("VendingPurchaseGate", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks vending machine purchases for players without a permission, optionally excluding machine owners")]
    public class VendingPurchaseGate : RustPlugin
    {
        private const string PermUse = "vendingpurchasegate.use";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Allow machine owners to always buy")]
            public bool AllowOwners { get; set; } = 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>
            {
                ["NoPermission"] = "You are not allowed to buy from vending machines.",
                ["OwnerAllowed"] = "Owners can always buy from their own machine."
            }, 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 OnBuyVendingItem(VendingMachine vendingMachine, BasePlayer player, int orderIndex, int amount)
        {
            if (vendingMachine == null || player == null) return null;

            if (permission.UserHasPermission(player.UserIDString, PermUse)) return null;

            if (config.AllowOwners && vendingMachine.OwnerID == player.userID.Get()) return null;

            player.ChatMessage(Msg("NoPermission", player.UserIDString));
            return true;
        }
    }
}

The plugin registers a single permission and checks it in OnBuyVendingItem. Players without vendingpurchasegate.use are refused, told why through a localized message and the purchase is cancelled. Optionally, a config flag limits the gate to machines the buyer does not own, so owners can still test their shop. Because the hook returns before the game reads currency, a denied player keeps their scrap and items and the machine stock is untouched. All feedback goes through the language file so the text can be translated or edited without recompiling.

Common use cases

  • Custom currency gates

    Refuse or allow a purchase based on a referenced economy plugin, a cooldown or a per-player daily limit before any scrap leaves the buyer's inventory.

  • Item blacklists per machine

    Look up the clicked sell order and block sales of specific shortnames on selected vending machines, for example on event arenas or roleplay hubs.

  • Purchase logging and auditing

    Record the buyer, order index, amount and machine position for later review, then return null so the sale completes normally.

Pitfalls

  • Returning false still cancels

    The patch treats any non-null return as a cancel. Returning false from the hook aborts the purchase just like true; only null lets it through.

  • Amount is client supplied

    The requested amount arrives from the client RPC. Trust it only for early rejection logic and let the game's own validation run by returning null when you do not want to block.

  • Not every buy goes through here

    Machines that were emptied or configurations using other purchase paths may not reach BuyItem. Pair this hook with CanPurchaseItem if your rule must hold in all cases.

Build a plugin with OnBuyVendingItem

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Custom currency gates using the OnBuyVendingItem hook, with a config file and a permission
Generate this plugin

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 OnBuyVendingItem

Generate