OnRentableShopBreakInComplete
OnRentableShopBreakInComplete is called when a player finishes the timed break-in of a rentable shop, right before the server spawns the shop's vending machine. Returning a non-null value cancels the break-in.
object OnRentableShopBreakInComplete(RentableShop rentableShop, BasePlayer player)Parameters
| Parameter | Type | Description |
|---|---|---|
rentableShop | RentableShop | The RentableShop entity being broken into. It is a BaseEntity (server-side rentable vending machine), so you can read its prefab, position and OwnerID, and later call it from timers. |
player | BasePlayer | The BasePlayer who finished the break-in timer. Use it for permissions, messaging, teleporting or logging the actor. |
Return value
Return null to let the break-in complete and the shop's vending machine spawn. Return any non-null object (a string, true, anything) to stop the original path: the server returns from CompleteBreakIn without spawning the machine, and the player's break-in progress for that shop is already cleared.
How it works
The hook fires inside RentableShop.CompleteBreakIn, after the player's break-in start timestamp has been found and removed from the shop's internal timer table. At that point the server has already verified the player was breaking in; the hook is the last gate before the shop is unsealed. Two things happen after the hook in the original path: the progress is compared against ApartmentCommands.breakinseconds * 0.9f (an early-returning sanity check that runs before the hook in the decompiled condition), and the shop spawns its VendingMachine from SpawnedVendingMachineRef. Because the check is if (time < threshold || Interface.CallHook(...) != null) return;, any non-null return aborts the completion: the break-in timestamp is gone, so no vending machine appears and the player has to start over. Declaring player more specifically (for example BasePlayer) is correct here since the game always passes a player; narrowing further to a custom type is not possible. Use the hook to record who broke in, to apply cooldowns or fees, or to deny completion for specific players.
Example plugin: Raid cooldown and logging for rentable shop break-ins
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("RentableShopBreakInGuard", "RustPluginGenerator", "1.0.0")]
[Description("Adds a cooldown and audit log to completed break-ins of rentable shops")]
public class RentableShopBreakInGuard : RustPlugin
{
private const string PermBypass = "rentableshopbreakinguard.bypass";
private PluginConfig config;
private readonly Dictionary<ulong, DateTime> lastBreakIn = new Dictionary<ulong, DateTime>();
private readonly List<string> history = new List<string>();
private class PluginConfig
{
[JsonProperty("Cooldown seconds per player")]
public int CooldownSeconds = 600;
[JsonProperty("Log completed break-ins to server console")]
public bool LogToConsole = true;
[JsonProperty("Max history entries kept in memory")]
public int MaxHistory = 200;
}
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>
{
["Cooldown"] = "You must wait {0} more seconds before breaking into another rentable shop.",
["Completed"] = "You broke into a rentable shop at {0}.",
["Denied"] = "You are not allowed to break into rentable shops here."
}, 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(PermBypass, this);
}
private object OnRentableShopBreakInComplete(RentableShop rentableShop, BasePlayer player)
{
if (rentableShop == null || player == null) return null;
bool bypass = permission.UserHasPermission(player.UserIDString, PermBypass);
DateTime now = DateTime.UtcNow;
DateTime last;
if (!bypass && lastBreakIn.TryGetValue(player.userID, out last))
{
double remaining = config.CooldownSeconds - (now - last).TotalSeconds;
if (remaining > 0)
{
player.ChatMessage(Msg("Cooldown", player.UserIDString, Math.Ceiling(remaining)));
return false;
}
}
lastBreakIn[player.userID] = now;
Record(rentableShop, player, now);
player.ChatMessage(Msg("Completed", player.UserIDString, now.ToString("HH:mm:ss") + " UTC"));
if (config.LogToConsole)
{
Puts($"{player.displayName} ({player.UserIDString}) completed break-in on shop {rentableShop.net.ID.Value}");
}
return null;
}
private void Record(RentableShop shop, BasePlayer player, DateTime at)
{
UnityEngine.Vector3 pos = shop.transform.position;
history.Add($"{at:u} | {player.UserIDString} | {player.displayName} | shop {shop.net.ID.Value} | {pos.x:F0},{pos.y:F0},{pos.z:F0}");
int max = config.MaxHistory > 0 ? config.MaxHistory : 200;
while (history.Count > max)
{
history.RemoveAt(0);
}
}
private void Unload()
{
lastBreakIn.Clear();
history.Clear();
}
}
}The plugin hooks OnRentableShopBreakInComplete and first checks whether the player is on a per-user cooldown stored in memory. If they are, it messages them through lang and returns false, which is non-null, so the server aborts the break-in. If the player is exempt by permission (rentableshopbreakinguard.bypass), the cooldown is skipped. Otherwise the plugin writes the completion time, the shop entity id, the shop position and the player id into a rolling history list, sends a message to the player and, when Log to server console is enabled in the config, calls Puts. Timestamps use DateTime.UtcNow and the in-memory state is cleared in Unload, so nothing is written to disk beyond the config.
Common use cases
Cooldown between shop break-ins
Reject completion when the same player finished a break-in recently by returning a non-null value, forcing them to wait before unsealing another shop.
Raid audit trail
Store the player id, shop entity id and world position of every completed break-in so admins can review who unsealed which shop and when.
Restricted zones or whitelists
Cancel completion for players outside a permission group or on a shop in a protected area, then send a chat reason using lang messages.
Pitfalls
Returning null by accident
The cancellation is driven by a non-null return. If you intend to block the break-in you must return something such as
falseor a string; returningnulllets the vending machine spawn.The timer entry is already removed
CompleteBreakInremoves the player's break-in start from the shop's dictionary before the hook runs. After a cancel the player restarts from zero, not from the remaining progress.The shop may not be a vending machine yet
The vending machine is spawned after the hook returns. Do not expect to find or loot it inside the hook; use
NextTickor a short timer if you need to act on the spawned entity.
Build a plugin with OnRentableShopBreakInComplete
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 OnRentableShopBreakInComplete
Generate