Skip to content
RustPluginGenerator

OnRentableShopBreakInCompleted

OnRentableShopBreakInCompleted is called right after a player finishes breaking into a RentableShop, once the break-in item has been consumed and the player has been added to the shop's intruder list.

Signature
void OnRentableShopBreakInCompleted(RentableShop rentableShop, BasePlayer player)

Parameters

Hook parameters
ParameterTypeDescription
rentableShopRentableShopThe RentableShop entity whose break-in was just completed. Use it to get the position, net ID or owner of the shop, and to react on the shop side (for example sending network updates or spawning effects).
playerBasePlayerThe BasePlayer who used the item and broke into the shop. Use it for permissions, notifications, logging and punishment logic.

Return value

The game ignores whatever this hook returns, and the break-in routine has already finished all of its steps when the hook is called. Returning null or any other value changes nothing about the shop, the player, or the item consumption.

How it works

This hook fires from RentableShop.CompleteBreakIn, after the game has already consumed the player's active item (UseItem), added the player to the shop's intruder list (AddIntruder), shown the blue break-in success toast, and sent the break-in analytics event. That means by the time your plugin runs, the break-in has already happened: the item is gone, the player is registered as an intruder, and the toast is on screen. You cannot use this hook to cancel the break-in; use the pre-hook variant for that if one is available.

Both parameters are the real game objects, so you can read rentableShop.net.ID.Value, rentableShop.transform.position, and anything on the player (permissions, team, held item, building privileges). Declaring a more specific parameter type would only narrow the hook, and both arguments are already at the most specific useful type, so keep the given types.

This fires once per successful break-in per player, not on ticks, but it can still fire often on busy servers, so avoid chatty logging. It is the right place for audit logging, cooldowns, heat tracking, or alerting nearby players.

Example plugin: Log break-ins and alert online admins

ShopBreakInAudit.cs
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Shop Break In Audit", "RustPluginGenerator", "1.0.0")]
    [Description("Logs RentableShop break-ins to a data file and alerts online admins")]
    public class ShopBreakInAudit : RustPlugin
    {
        private const string PermAlerts = "shopbreakinaudit.alerts";

        private PluginConfig config;
        private AuditData data;

        private class PluginConfig
        {
            [JsonProperty("Maximum stored break-in records")]
            public int MaxRecords = 250;

            [JsonProperty("Notify admins in chat")]
            public bool NotifyAdmins = true;
        }

        private class BreakInRecord
        {
            public string PlayerName;
            public string PlayerId;
            public ulong ShopNetId;
            public string Grid;
            public string TimeUtc;
        }

        private class AuditData
        {
            public List<BreakInRecord> Records = new List<BreakInRecord>();
        }

        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>
            {
                ["Alert"] = "[Shop] {0} ({1}) broke into a shop at grid {2}."
            }, 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(PermAlerts, this);
            data = Interface.Oxide.DataFileSystem.ReadObject<AuditData>(Name) ?? new AuditData();
            if (data.Records == null) data.Records = new List<BreakInRecord>();
        }

        private void OnRentableShopBreakInCompleted(RentableShop rentableShop, BasePlayer player)
        {
            if (rentableShop == null || player == null) return;

            string grid = FormatGrid(rentableShop.transform.position);

            data.Records.Insert(0, new BreakInRecord
            {
                PlayerName = player.displayName,
                PlayerId = player.UserIDString,
                ShopNetId = rentableShop.net != null ? rentableShop.net.ID.Value : 0UL,
                Grid = grid,
                TimeUtc = DateTime.UtcNow.ToString("u")
            });

            if (data.Records.Count > Mathf.Max(1, config.MaxRecords))
                data.Records.RemoveRange(config.MaxRecords, data.Records.Count - config.MaxRecords);

            Interface.Oxide.DataFileSystem.WriteObject(Name, data);

            if (!config.NotifyAdmins) return;

            string message = Msg("Alert", null, player.displayName, player.UserIDString, grid);
            foreach (BasePlayer target in BasePlayer.activePlayerList)
            {
                if (target == null || target.IsNpc) continue;
                if (!target.IsAdmin && !permission.UserHasPermission(target.UserIDString, PermAlerts)) continue;
                target.ChatMessage(message);
            }
        }

        private static string FormatGrid(Vector3 position)
        {
            if (TerrainMeta.Size.x <= 0f) return "unknown";
            int cellSize = Mathf.Max(1, Mathf.RoundToInt(TerrainMeta.Size.x / 26f));
            char letter = (char)('A' + Mathf.Clamp(Mathf.FloorToInt(position.x / cellSize), 0, 25));
            int number = Mathf.Clamp(Mathf.FloorToInt(position.z / cellSize), 0, 25);
            return letter + number.ToString();
        }

        private void Unload()
        {
            if (data != null)
                Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }
    }
}

The plugin registers a data file that stores a rolling list of shop break-ins. When OnRentableShopBreakInCompleted fires it builds a BreakInRecord from the shop's net ID and position plus the player's name and user ID, prepends it to the list, trims the list to the configured maximum, and writes the file. It then sends a localized chat warning to every connected admin, including the shop grid reference and the intruder's name. The player who broke in gets no message from this plugin, since the game already shows its own success toast. Unload flushes the data once more so nothing is lost during restarts.

Common use cases

  • Audit trail of who broke into which shop

    Store the player ID, shop net ID, grid reference and timestamp in a data file so staff can review break-ins after a wipe or after a report.

  • Alert admins or the owning team

    Notify online staff, or team members of the shop owner if you can resolve the owner, so they know a shop was broken into while they are online.

  • Cooldown or heat on repeated break-ins

    Track how often a player breaks into shops in a time window and apply a chat warning, a permission check, or a temporary block on further break-ins.

Pitfalls

  • The break-in cannot be cancelled

    The item is consumed, the intruder is added and the success toast is shown before the hook fires. Any blocking or refund logic must happen in the pre-break-in hook, not here.

  • Do not parse the player's held item

    The break-in item has already been used with UseItem when the hook runs, so the active item or its amount may no longer be what you expect. Capture that data in the earlier hook instead.

  • Hook fires during active play

    Avoid writing to chat or console for every call on busy servers and keep data writes small. Save the data file on this event but keep the list capped to a fixed length.

Build a plugin with OnRentableShopBreakInCompleted

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Audit trail of who broke into which shop using the OnRentableShopBreakInCompleted 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 OnRentableShopBreakInCompleted

Generate