OnVendingShopOpened
OnVendingShopOpened is called after a player's customer view of a vending machine has been opened, not before. Use OnVendingShopOpen if you need to approve or block the opening instead.
void OnVendingShopOpened(VendingMachine vendingMachine, BasePlayer player)Parameters
| Parameter | Type | Description |
|---|---|---|
vendingMachine | VendingMachine | The vending machine whose customer UI was just opened. Declared as VendingMachine here; the same hook also fires with InvisibleVendingMachine and NPCVendingMachine instances from other call sites, so declare the base type if you want one handler for all of them. |
player | BasePlayer | The BasePlayer who opened the shop screen. May be null in theory if the RPC arrived for a disconnected player, so check before using it. |
Return value
This hook does not use a return value. The patched call sites ignore whatever your method returns and the original code continues unchanged.
How it works
This hook fires at the end of the customer-open path, after the loot/shop UI is already shown to the player. There are three call sites in the server assembly:
VendingMachine.RPC_OpenShopafter theOccupiedCheckpasses andOpenShopruns, for normal player-placed vending machines.NPCTalking.OnConversationActionafterPlayerOpenLootis called on anInvisibleVendingMachine(NPC vending machines reachable through NPC conversation).TravellingVendor.SV_OpenMenuafterNPCVendingMachine.OpenShopfor the travelling vendor.
Because the same hook name is invoked with three different machine types, the parameter type you declare controls which call sites reach your method. Declaring VendingMachine catches only the first. Declaring InvisibleVendingMachine or NPCVendingMachine catches the NPC variants. To handle all three, use a base type that all of them derive from, such as BaseEntity, or write separate overloads. The hook is notification-only: the shop is already open when it fires, so it is the right place for analytics, UI overlays, or per-player cooldowns keyed on the machine.
Other signatures
Oxide also calls OnVendingShopOpened from other places in the game with these parameter types. Declare the overload you need, or several:
void OnVendingShopOpened(InvisibleVendingMachine vendingMachine, BasePlayer player)void OnVendingShopOpened(NPCVendingMachine vendingMachine, BasePlayer player)
Example plugin: Show a chat summary and log when a shop opens
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Vending Shop Audit", "RustPluginGenerator", "1.0.0")]
[Description("Logs and summarises vending machine shop opens per machine and player.")]
public class VendingShopAudit : RustPlugin
{
private const string PermUse = "vendingshopaudit.use";
private class PluginConfig
{
[JsonProperty("Notify player in chat")]
public bool NotifyPlayer { get; set; } = true;
[JsonProperty("Seconds between chat notifications per player")]
public float CooldownSeconds { get; set; } = 20f;
}
private class AuditData
{
public Dictionary<string, int> OpensPerMachine { get; set; } = new Dictionary<string, int>();
public Dictionary<string, int> OpensPerPlayer { get; set; } = new Dictionary<string, int>();
}
private PluginConfig config;
private AuditData data;
private readonly Dictionary<ulong, float> lastNotify = new Dictionary<ulong, float>();
private Timer saveTimer;
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>
{
["Opened"] = "Shop '{0}' at {1} has {2} sell offers.",
["Summary"] = "Tracked {0} shop opens across {1} machines.",
["NoPermission"] = "You do not have permission to use this command."
}, 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 void OnServerInitialized()
{
data = Interface.Oxide.DataFileSystem.ReadObject<AuditData>(Name) ?? new AuditData();
saveTimer = timer.Every(60f, SaveData);
}
private void Unload()
{
saveTimer?.Destroy();
SaveData();
}
private void SaveData()
{
if (data != null)
{
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
}
}
private void OnVendingShopOpened(VendingMachine vendingMachine, BasePlayer player)
{
if (vendingMachine == null || player == null || !player.IsConnected) return;
string machineId = vendingMachine.net.ID.Value.ToString();
data.OpensPerMachine.TryGetValue(machineId, out int machineCount);
data.OpensPerMachine[machineId] = machineCount + 1;
string playerId = player.UserIDString;
data.OpensPerPlayer.TryGetValue(playerId, out int playerCount);
data.OpensPerPlayer[playerId] = playerCount + 1;
if (!config.NotifyPlayer || !permission.UserHasPermission(playerId, PermUse)) return;
float now = Time.realtimeSinceStartup;
if (lastNotify.TryGetValue(player.userID, out float last) && now - last < config.CooldownSeconds) return;
lastNotify[player.userID] = now;
int sellOrders = vendingMachine.sellOrders?.sellOrders?.Count ?? 0;
Vector3 pos = vendingMachine.transform.position;
player.ChatMessage(Msg("Opened", playerId, vendingMachine.shopName, $"{pos.x:0},{pos.z:0}", sellOrders));
}
[ChatCommand("shopaudit")]
private void CmdShopAudit(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermUse))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
int total = 0;
foreach (var pair in data.OpensPerMachine) total += pair.Value;
player.ChatMessage(Msg("Summary", player.UserIDString, total, data.OpensPerMachine.Count));
}
}
}The plugin listens to OnVendingShopOpened and, when it fires, confirms the machine and player are valid. It reads the machine's display name via shopName and its position, then sends the player a short chat line with the machine location and the number of sell orders currently configured. A small per-player cooldown stops chat spam if the player toggles the shop repeatedly. The audit lines are buffered and written to oxide/data/VendingShopAudit on a one-minute timer and again in Unload, avoiding disk writes on every open. Permission vendingshopaudit.use gates the chat reply so admins can test without exposing it to all players.
Common use cases
Track shop traffic per machine
Increment a counter keyed by the machine's
net.ID.Valueeach time it fires, then rank machines by opens in an admin command. Because it fires after the UI is shown, the count reflects successful opens only.Attach a per-player cooldown
Record the timestamp per player to stop players from spamming the customer panel and repeatedly triggering other hooks like
OnVendingShopOpenorCanPurchaseItemchecks.Open a follow-up CUI panel
Push your own overlay panel for the player on top of the vending screen, e.g. to show stock of similar machines in range from your cached data.
Pitfalls
Hook fires after the shop is open
If you want to prevent the shop from opening or alter its stock before it shows, use
OnVendingShopOpeninstead.OnVendingShopOpenedcannot block or modify the interaction.Parameter type filters the call sites
Declaring
VendingMachineonly receives player-placed machines. NPC machines reach the hook asInvisibleVendingMachineorNPCVendingMachine, and Oxide drops calls whose argument is not an instance of your declared type.Player and machine may be null
RPC_OpenShopalready callsOccupiedCheckfirst, but hook parameters can still arrive null during a disconnect race. Guard both before touchingplayer.UserIDStringorvendingMachine.shopName.
Build a plugin with OnVendingShopOpened
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 OnVendingShopOpened
Generate