OnRentableShopClosed
OnRentableShopClosed is called after a RentableShop finishes closing its session, once the renter has been evicted and the shop's rental state has been reset.
void OnRentableShopClosed(RentableShop rentableShop, bool notify)Parameters
| Parameter | Type | Description |
|---|---|---|
rentableShop | RentableShop | The RentableShop entity whose shop session just closed. Use it to read the entity position, owner and short prefab name. |
notify | bool | True when the close was triggered in a way meant to notify the previous renter (for example an admin eviction), false for a normal end-of-session close. The game has already handled the notification before this hook fires. |
Return value
The return value is ignored. Calling the hook cannot stop the shop from closing, prevent the notification, or restore the previous renter; the shop has already been reset when the hook fires.
How it works
This hook fires at the end of RentableShop.OnShopClosed, after the game has already raised the shop-closed notification to the renter, reset ShopOwnerId to zero and cancelled the DeductRent invoke. That means by the time your code runs, the shop no longer belongs to anyone and rent collection has stopped.
The first argument is the RentableShop entity itself. Read rentableShop.transform.position for a location, rentableShop.net.ID.Value for a stable identifier, or rentableShop.ShortPrefabName if you plan to store it. The second argument tells you whether the close path requested a notification; it is a plain bool and the notification has already been sent, so it is informational only.
Oxide dispatch matches parameters by type, so declaring rentableShop as BaseEntity or BaseNetworkable still receives the call, but RentableShop is the exact type and you should keep it unless you deliberately want to ignore non-shop entities (they cannot occur here). The return value is ignored, so there is nothing to cancel.
The hook pair OnRentableShopClose / OnRentableShopClosed is easy to confuse: the first fires before the shutdown runs, the second fires after the state has been cleaned up. Use this one whenever you need the final, post-reset state.
Example plugin: Log closed shop sessions to an admin channel
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Rentable Shop Close Logger", "RustPluginGenerator", "1.0.0")]
[Description("Records closed rentable shop sessions and lets admins read them back.")]
public class RentableShopCloseLogger : RustPlugin
{
private const string PermRead = "rentableshopcloselogger.read";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Maximum stored entries")]
public int MaxEntries = 500;
}
private class ShopEntry
{
[JsonProperty("Network ID")]
public ulong NetId;
[JsonProperty("Prefab")]
public string Prefab;
[JsonProperty("Position")]
public string Position;
[JsonProperty("Notify flag")]
public bool Notify;
[JsonProperty("Closed at (UTC)")]
public string ClosedAt;
}
private List<ShopEntry> entries = new List<ShopEntry>();
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);
private void Init()
{
permission.RegisterPermission(PermRead, this);
entries = Interface.Oxide.DataFileSystem.ReadObject<List<ShopEntry>>(Name) ?? new List<ShopEntry>();
}
private void Unload()
{
SaveEntries();
}
private void OnRentableShopClosed(RentableShop rentableShop, bool notify)
{
if (rentableShop == null || rentableShop.net == null) return;
if (entries.Count >= Mathf.Max(1, config.MaxEntries))
{
entries.RemoveRange(0, entries.Count - config.MaxEntries + 1);
}
Vector3 pos = rentableShop.transform.position;
entries.Add(new ShopEntry
{
NetId = rentableShop.net.ID.Value,
Prefab = rentableShop.ShortPrefabName,
Position = string.Format("{0:F1} {1:F1} {2:F1}", pos.x, pos.y, pos.z),
Notify = notify,
ClosedAt = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")
});
SaveEntries();
}
private void SaveEntries()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, entries);
}
[ConsoleCommand("shoplog.recent")]
private void CmdRecent(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermRead))
{
arg.ReplyWith("You do not have permission to read the shop log.");
return;
}
int count = arg.GetInt(0, 5);
if (count < 1) count = 1;
if (count > 20) count = 20;
if (entries.Count == 0)
{
arg.ReplyWith("No shop sessions have been logged yet.");
return;
}
int start = Math.Max(0, entries.Count - count);
for (int i = start; i < entries.Count; i++)
{
ShopEntry e = entries[i];
arg.ReplyWith(string.Format("[{0}] {1} at {2} notify={3} ({4})",
e.NetId, e.Prefab, e.Position, e.Notify, e.ClosedAt));
}
}
}
}The plugin keeps a rolling log of closed rental shops in oxide/data, capped by MaxEntries in the config. When OnRentableShopClosed fires, it records the shop's network ID, prefab name, world position and the notify flag, plus the server time. An admin-only console command reads recent rows back. Entries are written with Interface.Oxide.DataFileSystem and the list is trimmed once it grows past the configured cap, so the data file stays small. The hook itself stays cheap: it appends one object to a list and returns nothing, which matters because servers with many rental shops can close sessions frequently.
Common use cases
Rental analytics
Append every closed session to a data file so you can count how often each shop is rented and how long renters stay.
Discord or chat alerts for admins
Notify staff when a shop stops being rented, especially when the close path used the notify flag, which often indicates an admin action.
Clean up per-shop side data
Remove stored overrides such as custom pricing or welcome text keyed by
rentableShop.net.ID.Valueonce the shop closes.
Pitfalls
Not a cancellation hook
The hook fires after the renter is evicted and rent deduction is cancelled, so you cannot abort the close. Pair it with
OnRentableShopCloseif you need a pre-close veto point.The notify flag is not a reason
notifyonly records whether the close path asked for the renter to be notified. It is false for ordinary end-of-session closures, so do not treat it as a reliable indicator of who closed the shop.Entity may already be gone
If the shop is being despawned or the map is wiping, treat
rentableShopdefensively and check for null before readingtransformornet.
Build a plugin with OnRentableShopClosed
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 OnRentableShopClosed
Generate