OnRentableShopOpened
OnRentableShopOpened is called after a player has paid scrap to open a rentable shop in the apartments area, once the shop rent has started or been extended and the scrap payment has been processed.
void OnRentableShopOpened(RentableShop rentableShop, BasePlayer byPlayer)Parameters
| Parameter | Type | Description |
|---|---|---|
rentableShop | RentableShop | The RentableShop instance that was just opened (or extended) with scrap. Use it to read its position, owner and net ID for notifications or logging. |
byPlayer | BasePlayer | The BasePlayer who paid the scrap to open the shop. Can be null in theory, so check it before touching inventory or chat. |
Return value
The return value is ignored. The hook is patched with a plain continue, and the original OnShopOpened method returns void, so returning null, false or any other value has no effect on the game's behavior.
How it works
This hook fires from RentableShop.OnShopOpened, immediately after the game has taken the open cost from the player (byPlayer.inventory.UseAmount(ItemManager.Items.Scrap, openScrapCost)), moved a bulk of scrap (roughly twelve hours of rent) out of the player's inventory into the shop's invisible vending machine, and reported the event to Facepunch's analytics. By the time the hook runs, the payment has already succeeded; you cannot cancel or block the opening from here.
Use rentableShop to identify the shop that changed state (its transform position, net.ID.Value, and OwnerID are available) and byPlayer to message the player, adjust balances in an economy plugin, or record who is renting which shop. Both parameters are concrete types, so you do not need to narrow them; declaring BasePlayer is already as specific as the game passes.
The hook is called once per successful opening or extension, so it is a good spot to fire chat feedback, telemetry, or a timer that later checks shop status. Do not call it per-tick or expect it when the payment fails, since failure exits before the hook.
Example plugin: Notify the team and log shop rentals
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("RentableShopOpenNotice", "RustPluginGenerator", "1.0.0")]
[Description("Notifies teammates and logs when a player opens a rentable shop.")]
public class RentableShopOpenNotice : RustPlugin
{
private const string PermBroadcast = "rentableshopopennotice.broadcast";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Notify team members")]
public bool NotifyTeam { get; set; } = true;
[JsonProperty("Log to oxide log file")]
public bool LogToFile { 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>
{
["Notice.Renter"] = "You opened the shop at {0}.",
["Notice.Team"] = "{0} opened a rentable shop at {1}."
}, 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(PermBroadcast, this);
}
private void OnRentableShopOpened(RentableShop rentableShop, BasePlayer byPlayer)
{
if (rentableShop == null || byPlayer == null) return;
Vector3 pos = rentableShop.transform.position;
string grid = GridRef(pos);
if (config.LogToFile)
{
Interface.Oxide.LogInfo(string.Format(
"[RentableShopOpenNotice] {0} ({1}) opened shop {2} at {3}",
byPlayer.displayName, byPlayer.UserIDString,
rentableShop.net.ID.Value, grid));
}
byPlayer.ChatMessage(Msg("Notice.Renter", byPlayer.UserIDString, grid));
if (!config.NotifyTeam || !permission.UserHasPermission(byPlayer.UserIDString, PermBroadcast)) return;
if (byPlayer.currentTeam == 0) return;
var team = RelationshipManager.ServerInstance.FindTeam(byPlayer.currentTeam);
if (team == null) return;
foreach (ulong memberId in team.members)
{
if (memberId == byPlayer.userID.Get()) continue;
BasePlayer member = BasePlayer.FindByID(memberId);
if (member == null || !member.IsConnected) continue;
member.ChatMessage(Msg("Notice.Team", member.UserIDString, byPlayer.displayName, grid));
}
}
private static string GridRef(Vector3 position)
{
float x = position.x;
float z = position.z;
int gridX = Mathf.FloorToInt((x + 2048f) / 146.3f);
int gridZ = Mathf.FloorToInt((z + 2048f) / 146.3f);
return string.Format("{0}{1}", (char)('A' + Mathf.Clamp(gridX, 0, 25)), Mathf.Clamp(gridZ, 0, 25));
}
}
}The plugin listens for OnRentableShopOpened, null-checks the player and shop, and then does two things. First it writes a short line to the Oxide log file with the renter's name and the shop grid position so admins can audit rentals. Second, if the renter belongs to a team, it sends a team chat message via ChatMessage to the player and a broadcast to the team leader using SendReply, so teammates know the shop was opened. Messages go through the language file, so admins can translate them. A permission rentableshopopennotice.broadcast gates the team notifications so the feature can be disabled per player group without unloading the plugin.
Common use cases
Team rental alerts
Message the renter's teammates when they open a shop so groups know who is spending scrap on apartments.
Economy reconciliation
Forward the renter and shop identity to an economy plugin (via
[PluginReference]) to refund or charge a virtual currency alongside the scrap payment.Rental audit log
Record the renter, shop net ID and grid reference to
oxide/logsso admins can review disputed rents or griefing reports.
Pitfalls
Fires after payment
The hook runs after scrap is taken and moved into the vending machine, so returning a value or throwing here cannot prevent the charge.
Null player is possible
Always null-check
byPlayerand its inventory before reading or messaging; the game passes the player reference without a guard in the caller.Do not assume shop type
RentableShopis a specific entity; do not castrentableShopto other container types or you will get null at runtime.
Build a plugin with OnRentableShopOpened
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 OnRentableShopOpened
Generate