OnRentableShopClose
OnRentableShopClose is called when a RentableShop closes its store, before the game clears the shop's open flag. Returning a non-null value stops the original close logic.
object OnRentableShopClose(RentableShop rentableShop, bool notify)Parameters
| Parameter | Type | Description |
|---|---|---|
rentableShop | RentableShop | The RentableShop entity that is being closed. Use it to read the shop's position, ownership and network ID, and to find out which player rented it. |
notify | bool | True when the close should send the usual shutdown message to the renter. This mirrors the game method's own notify argument. |
Return value
Return null (or nothing) to let the game finish closing the store, including clearing the open flag. Return any non-null value to abort the rest of RentableShop.CloseStore, which leaves the shop in its current state instead of completing the close.
How it works
The hook fires inside RentableShop.CloseStore, which the server calls when a rented shop session ends - the renter leaves the store, the rental timer expires, or the shop is shut down by other game logic. It runs before the shop's Flags.On flag is cleared, so at hook time the shop still counts as open. rentableShop is the entity being closed; use its net.ID.Value, OwnerID and position to identify the shop and the renter. notify tells you whether the close was requested with the shutdown notification, which is useful if you want to add your own message without duplicating the game's.
Declaring the first parameter as RentableShop is required to receive the call at all; a looser type such as BaseEntity still matches, but you lose the shop-specific members. Because the hook sits on a patched return path, it is not a notification-only hook: any non-null return suppresses the rest of the close routine, so treat it as a veto point rather than a logging point. Keep the body fast and do not print warnings here, since shop closes are real game events and this hook can fire during normal play.
Example plugin: Announce shop closures to the team and log them
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Shop Close Announcer", "RustPluginGenerator", "1.0.0")]
[Description("Announces rentable shop closures to the owner's team and logs them to a data file")]
public class ShopCloseAnnouncer : RustPlugin
{
private const string PermLog = "shopcloseannouncer.log";
private PluginConfig config;
private ShopLog log;
private class PluginConfig
{
[JsonProperty("Announce to team")]
public bool AnnounceToTeam = true;
[JsonProperty("Maximum log entries")]
public int MaxLogEntries = 200;
}
private class ShopLog
{
public List<ShopEntry> Entries = new List<ShopEntry>();
}
private class ShopEntry
{
public ulong OwnerId;
public string OwnerName;
public ulong ShopId;
public string ClosedAt;
}
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>
{
["Announce"] = "{0} closed their rental shop.",
["Logged"] = "Shop close logged. Total entries: {0}"
}, 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(PermLog, this);
}
private void OnServerInitialized()
{
log = Interface.Oxide.DataFileSystem.ReadObject<ShopLog>(Name) ?? new ShopLog();
}
private object OnRentableShopClose(RentableShop rentableShop, bool notify)
{
if (rentableShop == null) return null;
ulong ownerId = rentableShop.OwnerID;
BasePlayer owner = ownerId != 0 ? BasePlayer.FindByID(ownerId) : null;
string ownerName = owner != null ? owner.displayName : "An unknown player";
if (config.AnnounceToTeam && owner != null && owner.currentTeam != 0)
{
var team = RelationshipManager.ServerInstance.FindTeam(owner.currentTeam);
if (team != null)
{
foreach (ulong memberId in team.members)
{
if (memberId == ownerId) continue;
BasePlayer member = BasePlayer.FindByID(memberId);
if (member == null || !member.IsConnected) continue;
member.ChatMessage(Msg("Announce", member.UserIDString, ownerName));
}
}
}
if (log == null) return null;
log.Entries.Add(new ShopEntry
{
OwnerId = ownerId,
OwnerName = ownerName,
ShopId = rentableShop.net != null ? rentableShop.net.ID.Value : 0UL,
ClosedAt = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")
});
while (log.Entries.Count > Mathf.Max(1, config.MaxLogEntries))
{
log.Entries.RemoveAt(0);
}
Interface.Oxide.DataFileSystem.WriteObject(Name, log);
return null;
}
[ChatCommand("shoplog")]
private void CmdShopLog(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermLog))
{
player.ChatMessage("You do not have permission to view the shop log.");
return;
}
int count = log != null ? log.Entries.Count : 0;
player.ChatMessage(Msg("Logged", player.UserIDString, count));
}
}
}The plugin listens for shop closes and resolves the shop's owner through OwnerID. If the owner is an awake player with a team, every teammate gets a chat line naming the closing player, so groups know when a teammate's rental ended. The event is also appended to a small log list saved under oxide/data through Interface.Oxide.DataFileSystem, capped at the configured maximum so the file does not grow without limit. Messages go through the language file, and a config option lets admins turn the broadcast off. The hook always returns null so the shop still finishes its normal close path.
Common use cases
Team notifications for rental endings
Tell a player's teammates when their shop session closes so the group can plan around the lost access. Send text through the language file and respect a config toggle.
Shop usage logging
Record shop network ID, owner and timestamp on every close to build a history of rental activity on the server. Persist entries with the data file system rather than printing to console.
Closing effects
Play a short effect at the shop position or clean up custom markers you attached when it opened. Use
Effect.server.RunatrentableShop.transform.position.
Pitfalls
A non-null return blocks the close
The hook is installed on a return-value check around the close routine. Returning anything other than null aborts the remaining close logic, so always return null unless you intentionally want to keep the shop open.
Do not drop the shop argument type
Declaring the parameter as
BaseEntitystill matches, but you lose access to shop-specific members and the intent is unclear. KeepRentableShopso the method is only invoked for actual shops.Owner may be offline
OwnerIDis a ulong and the renting player can be asleep or disconnected. Resolve the player withBasePlayer.FindByIDand check the result before sending messages.
Build a plugin with OnRentableShopClose
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 OnRentableShopClose
Generate