OnApartmentMasterKeyPurchased
OnApartmentMasterKeyPurchased is called after a player buys an apartment master key from NPCApartmentSecurity, once the key item has already been created and handed to the player's inventory.
void OnApartmentMasterKeyPurchased(BasePlayer player, Item item)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The BasePlayer who bought the master key. Matches the buyer passed into NPCApartmentSecurity.OnPurchaseKey. |
item | Item | The Item instance for the master key that was just created and already given to the player through GiveItem. It has item ownership recorded as a vendor sale. |
Return value
The return value is ignored. The original method is void and the hook call is the final statement in the purchase branch, so returning null or any other value has no effect on the game.
How it works
This hook fires inside NPCApartmentSecurity.OnPurchaseKey, immediately after the game creates the master key item with ItemManager.Create, records item ownership as a vendor sale, and gives it to the player via GiveItem. Because it is the last statement in the success branch, it only runs when the item creation succeeded.
The hook is observational: the purchase and item transfer are already done. player is the buyer, and item is the live key item that now sits in the buyer's inventory (or on the ground if the inventory was full, since GiveItem drops overflow). You can read item.skin, item.amount, or item.info.itemid to identify the key, and use player.userID or player.UserIDString to key your own data.
Because there is only one injection site, you cannot narrow which calls you receive by declaring a more specific parameter type; both arguments are always the exact player and key item. If you need to react before the key is given, use OnApartmentMasterKeyPurchase or CanAffordApartmentMasterKey instead.
Example plugin: Track and reward apartment master key purchases
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Apartment Key Tracker", "RustPluginGenerator", "1.0.0")]
[Description("Tracks apartment master key purchases and rewards repeat buyers.")]
public class ApartmentKeyTracker : RustPlugin
{
private const string PermUse = "apartmentkeytracker.use";
private PluginConfig config;
private StoredData data;
private Timer saveTimer;
private bool dirty;
private class PluginConfig
{
[JsonProperty("Reward every N purchases (0 disables)")]
public int RewardEvery { get; set; } = 3;
[JsonProperty("Reward item shortname")]
public string RewardShortname { get; set; } = "scrap";
[JsonProperty("Reward amount")]
public int RewardAmount { get; set; } = 50;
}
private class StoredData
{
[JsonProperty("Purchase counts by user id")]
public Dictionary<string, int> Counts { get; set; } = new Dictionary<string, int>();
}
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>
{
["Rewarded"] = "Thanks for your purchase! You reached {0} master keys and received {1} x{2}.",
["NoPermission"] = "You do not have permission to use apartment keys here."
}, this);
}
private string Msg(string key, string userId = null, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
private void Init()
{
permission.RegisterPermission(PermUse, this);
data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
}
private void Unload()
{
saveTimer?.Destroy();
SaveData();
}
private void OnApartmentMasterKeyPurchased(BasePlayer player, Item item)
{
if (player == null) return;
if (!data.Counts.ContainsKey(player.UserIDString))
data.Counts[player.UserIDString] = 0;
data.Counts[player.UserIDString]++;
dirty = true;
QueueSave();
if (config.RewardEvery <= 0) return;
if (data.Counts[player.UserIDString] % config.RewardEvery != 0) return;
var reward = ItemManager.CreateByName(config.RewardShortname, config.RewardAmount);
if (reward == null) return;
player.GiveItem(reward);
player.ChatMessage(Msg("Rewarded", player.UserIDString,
data.Counts[player.UserIDString], reward.info.displayName.english, config.RewardAmount));
}
private void QueueSave()
{
if (saveTimer != null && !saveTimer.Destroyed) return;
saveTimer = timer.Once(5f, () =>
{
saveTimer = null;
SaveData();
});
}
private void SaveData()
{
if (!dirty) return;
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
dirty = false;
}
}
}The plugin keeps a per-player purchase count in oxide/data and grants a bonus item every N keys (default 3), giving buyers a small incentive to keep shopping.
OnApartmentMasterKeyPurchased looks up the player's record, increments the counter, and saves via a timer so rapid purchases are batched. When the count hits the configured threshold it gives a reward item with GiveItem and resets the counter, sending a chat message through the language file. A config file controls the threshold, reward shortname, and reward amount. Data is saved on unload, and the plugin null-checks both the hook arguments so unexpected calls cannot throw.
Common use cases
Reward repeat buyers
Count master key purchases per player and hand out an item, currency, or permission after a threshold to encourage apartment use on your server.
Audit key sales
Log each purchase with the buyer's Steam ID and the key's skin to a data file so admins can trace who bought what during an event.
Restrict or flag keys by group
Use the buyer's permission group to post a staff alert when a key is bought outside normal access, or mark the item so a separate system can identify it later.
Pitfalls
The key is already delivered
By the time this hook runs, the item is in the player's inventory or dropped on the ground. Do not try to cancel the purchase here; use the earlier purchase hooks for that.
Do not assume the item is in the backpack
If the buyer's inventory was full,
player.GiveItemdrops the key at the player's feet. Checkitem.parentbefore trying to move or read its slot.Avoid heavy work in the callback
The hook runs on the main thread during the NPC purchase flow. Keep data writes batched with a timer instead of hitting
DataFileSystemsynchronously on every purchase.
Build a plugin with OnApartmentMasterKeyPurchased
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 OnApartmentMasterKeyPurchased
Generate