Skip to content
RustPluginGenerator

CanBuild

CanBuild is called from Planner.DoBuild right after the client's build request has been decoded into a Construction.Target, before the planner decides on parenting and placement. Returning a non-null value aborts the build.

Signature
object CanBuild(Planner planner, Construction construction, Construction.Target target)

Parameters

Hook parameters
ParameterTypeDescription
plannerPlannerThe Planner entity (building plan) that the player is holding when the placement request arrives. Use it to read OwnerID, the active item, or its position.
constructionConstructionThe Construction component describing what is being built: construction grade, prefab, the deployable or building block being placed.
targetConstruction.TargetThe Construction.Target holding the resolved placement data: entity, position, rotation, normal, socket, snapping state, and the valid flag set just before the hook fires.

Return value

Returning null lets the original Planner.DoBuild continue with parenting and placement. Returning any non-null object makes the patched method return immediately, cancelling the placement for this request; the planner never reaches its ShouldParent logic, so no entity is spawned and no resources are consumed.

How it works

CanBuild fires once per build attempt, when a player sends a build request through a Planner. By the time it runs, the server has already copied the client's socket, snapping mode, snap position and snap rotation into target and set target.valid to true; the hook is checked before ShouldParent and the rest of the placement logic runs.

Use the parameters to inspect the whole request:

  • planner is the building plan entity; planner.GetOwnerPlayer() gives the builder (may be null for edge cases).
  • construction is the planned structure component; construction.grade, construction.currentGrade and construction.canRotate describe the block.
  • target carries entity, position, rotation, normal, socket, snappingMode, isSnapped, snappedPosition, snappedRotation and valid; target.entity is the prefab that would be spawned.

Narrow the signature by declaring Planner, Construction or Construction.Target only if you need a specific subtype; otherwise keep the exact types. Returning non-null blocks only this one build attempt; the player is not notified, so send feedback yourself.

Example plugin: Block building while inside a no-build zone

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

namespace Oxide.Plugins
{
    [Info("CanBuild Zone", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks structure placement inside configured no-build zones.")]
    public class CanBuildZone : RustPlugin
    {
        private const string PermBypass = "canbuildzone.bypass";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Zones")]
            public List<Zone> Zones = new List<Zone>();

            [JsonProperty("Zone message radius")]
            public float MessageRadius = 0f;
        }

        private class Zone
        {
            [JsonProperty("Name")]
            public string Name = "spawn";

            [JsonProperty("Center X")]
            public float X = 0f;

            [JsonProperty("Center Y")]
            public float Y = 0f;

            [JsonProperty("Center Z")]
            public float Z = 0f;

            [JsonProperty("Radius")]
            public float Radius = 100f;

            [JsonProperty("Allowed Team ID (0 = none)")]
            public ulong AllowedTeam = 0;
        }

        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>
            {
                ["NoBuildZone"] = "You cannot build here ({0})."
            }, 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 CanBuild(Planner planner, Construction construction, Construction.Target target)
        {
            if (planner == null) return null;
            if (config.Zones == null || config.Zones.Count == 0) return null;

            var player = planner.GetOwnerPlayer();
            if (player == null) return null;
            if (player.IsAdmin || permission.UserHasPermission(player.UserIDString, PermBypass)) return null;

            Vector3 position = target.position;
            for (int i = 0; i < config.Zones.Count; i++)
            {
                Zone zone = config.Zones[i];
                if (zone == null || zone.Radius <= 0f) continue;
                var center = new Vector3(zone.X, zone.Y, zone.Z);
                if (Vector3.Distance(position, center) > zone.Radius) continue;
                if (zone.AllowedTeam != 0 && player.currentTeam == zone.AllowedTeam) continue;

                player.ChatMessage(Msg("NoBuildZone", player.UserIDString, zone.Name));
                return true;
            }

            return null;
        }
    }
}

The plugin defines a configurable list of no-build zones (center point, radius, optional allowed team ID). In the CanBuild hook the planner's owner player is resolved; for each zone, the target build position (target.position) is compared with the zone center. Zones with radius <= 0 are ignored. If the position is within the radius and the player is not on the zone's allowed team, the hook returns a non-null value to abort the placement and the player gets a chat notice through the language file. Admins and players with the canbuildzone.bypass permission skip the check.

Common use cases

  • No-build zones around spawn

    Block placement near spawn, event areas or monuments and inform the player via chat.

  • Building restrictions per team

    Deny placement when the planner owner is in a team with a raid in progress, or for a specific team ID that the config lists.

  • Height and terrain restrictions

    Reject structures placed above a Y threshold, below sea level or inside a no-PvP region controlled by your own data files.

Pitfalls

  • Null planner owner

    planner and planner.GetOwnerPlayer() can be null in some code paths; always null-check before reading OwnerID or sending a message.

  • No automatic feedback

    Returning a value silently cancels the placement. Send a chat or CUI message yourself, or the player assumes the server is laggy.

  • Hook fires for every block

    CanBuild runs on each build request, including spam. Keep the callback cheap: no file I/O, no per-call logging, no heavy LINQ over the world.

Plugin recipes that use CanBuild

Build a plugin with CanBuild

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

Prompt: No-build zones around spawn using the CanBuild 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 CanBuild

Generate