Skip to content
RustPluginGenerator

OnCupboardProtectionCalculated

OnCupboardProtectionCalculated is called by BuildingPrivlidge.GetProtectedMinutes when the game recomputes a tool cupboard's protection time, receiving the cupboard and the fresh minute value.

Signature
void OnCupboardProtectionCalculated(BuildingPrivlidge privilege, float cachedProtectedMinutes)

Parameters

Hook parameters
ParameterTypeDescription
privilegeBuildingPrivlidgeThe BuildingPrivlidge (tool cupboard) whose protection window was just recalculated. Use it to read OwnerID, net.ID, position or the authorized players list.
cachedProtectedMinutesfloatThe newly computed protection time in minutes, cached by the game for the next 60 seconds. This is the value GetProtectedMinutes will return, so you can read it for display or stats.

Return value

The return value is ignored: the method returns cachedProtectedMinutes regardless of what your hook returns. To change the protection window you must change the cupboard's stored resources or its building, not return a value.

How it works

The hook fires inside BuildingPrivlidge.GetProtectedMinutes. The game caches the result and only recalculates after a 60 second interval (nextProtectedCalcTime), so this hook runs at most once per minute per cupboard and only when something actually calls GetProtectedMinutes in that window. The calculation is based on the stored upkeep resources and the building that the cupboard covers.

privilege is the cupboard entity itself: read OwnerID, net.ID.Value, transform.position or IsAuthed(player) from it. cachedProtectedMinutes is the float the method is about to return, expressed in minutes, so you can forward it to a UI, log it, or publish it to a web panel via data files.

You cannot narrow this by parameter type in a useful way, since both arguments are always the same types. If you need to detect when the value changes, store the last known value per cupboard and compare it in your hook. The return value is ignored, so the game's protection time cannot be modified here.

Note that the hook can fire for a cupboard that is already decayed or has zero upkeep stored; do not assume the value is positive.

Example plugin: Track cupboard protection changes and warn owners

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

namespace Oxide.Plugins
{
    [Info("Cupboard Protection Monitor", "RustPluginGenerator", "1.0.0")]
    [Description("Warns authorized players when a tool cupboard's protection time drops below a threshold")]
    public class CupboardProtectionMonitor : RustPlugin
    {
        private const string PermUse = "cupboardprotectionmonitor.use";

        private PluginConfig config;
        private readonly Dictionary<ulong, float> lastMinutes = new Dictionary<ulong, float>();
        private readonly Dictionary<ulong, float> lastWarn = new Dictionary<ulong, float>();

        private class PluginConfig
        {
            [JsonProperty("Warn threshold (minutes)")]
            public float Threshold { get; set; } = 24f;

            [JsonProperty("Warning cooldown (seconds)")]
            public float Cooldown { get; set; } = 600f;
        }

        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>
            {
                ["LowProtection"] = "{0}: cupboard at {1} has only {2:0.#} minutes of protection left.",
                ["NoData"] = "No protection data recorded for your cupboard yet.",
                ["QueryResult"] = "Your cupboard has {0:0.#} minutes of protection cached."
            }, 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(PermUse, this);
            lastMinutes.Clear();
            lastWarn.Clear();
        }

        private void OnCupboardProtectionCalculated(BuildingPrivlidge privilege, float cachedProtectedMinutes)
        {
            if (privilege == null || privilege.IsDestroyed) return;

            ulong id = privilege.net.ID.Value;
            float previous;
            lastMinutes.TryGetValue(id, out previous);
            lastMinutes[id] = cachedProtectedMinutes;

            if (cachedProtectedMinutes >= config.Threshold || previous < config.Threshold) return;

            float last;
            float now = Time.realtimeSinceStartup;
            if (lastWarn.TryGetValue(id, out last) && now - last < config.Cooldown) return;
            lastWarn[id] = now;

            Vector3 pos = privilege.transform.position;
            foreach (BasePlayer player in BasePlayer.activePlayerList)
            {
                if (player == null || !player.IsConnected) continue;
                if (!privilege.IsAuthed(player)) continue;
                player.ChatMessage(Msg("LowProtection", player.UserIDString,
                    pos.ToString("0"), cachedProtectedMinutes));
            }
        }

        [ChatCommand("cupboardprot")]
        private void CmdCupboardProt(BasePlayer player, string command, string[] args)
        {
            if (player == null || !permission.UserHasPermission(player.UserIDString, PermUse))
            {
                SendReply(player, "You do not have permission to use this command.");
                return;
            }

            BuildingPrivlidge privilege = player.GetBuildingPrivilege();
            if (privilege == null)
            {
                SendReply(player, Msg("NoData", player.UserIDString));
                return;
            }

            float minutes;
            if (!lastMinutes.TryGetValue(privilege.net.ID.Value, out minutes))
            {
                SendReply(player, Msg("NoData", player.UserIDString));
                return;
            }

            SendReply(player, Msg("QueryResult", player.UserIDString, minutes));
        }

        private void Unload()
        {
            lastMinutes.Clear();
            lastWarn.Clear();
        }
    }
}

The plugin stores the last seen protection value per cupboard in a dictionary keyed by net.ID.Value. On every OnCupboardProtectionCalculated it compares the new value with the stored one. If the value dropped below the configured threshold while previously being at or above it, it sends a chat message to every currently connected player that is authorized on the cupboard. To avoid spam the warning is throttled per cupboard using a second dictionary of timestamps and a configurable cooldown. A chat command cupboardprot lets players query their own cupboard's last known value. Unload clears the dictionaries so no stale data survives a reload.

Common use cases

  • Low-upkeep warnings

    Notify authorized players when a cupboard's protection time falls under a threshold so they can add resources before decay starts.

  • Base dashboard data

    Write the latest protection minutes per cupboard to a data file so an external website can show base health.

  • Audit logging

    Log changes in protection time with the owning player's ID to spot bases that are being drained or neglected.

Pitfalls

  • Not called every tick

    The game caches the value for 60 seconds and only calls GetProtectedMinutes when something asks for it. Do not expect a steady callback for idle cupboards.

  • Return value has no effect

    Because the hook returns void from the game's perspective, returning a float from your hook changes nothing. Modify stored resources if you need to alter the window.

  • Destroyed cupboards

    If the cupboard is destroyed or the building decays between calls, the entity may already be invalid. Check privilege.IsDestroyed before using it.

Build a plugin with OnCupboardProtectionCalculated

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

Prompt: Low-upkeep warnings using the OnCupboardProtectionCalculated 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 OnCupboardProtectionCalculated

Generate