Skip to content
RustPluginGenerator

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.

Signature
object OnRentableShopBreakInComplete(RentableShop rentableShop, BasePlayer player)

Parameters

Hook parameters
ParameterTypeDescription
rentableShopRentableShopThe 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.
playerBasePlayerThe 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

RentableShopBreakInGuard.cs
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 false or a string; returning null lets the vending machine spawn.

  • The timer entry is already removed

    CompleteBreakIn removes 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 NextTick or 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.

Prompt: Cooldown between shop break-ins using the OnRentableShopBreakInComplete hook, with a config file and a permission
Generate this plugin

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