Skip to content
RustPluginGenerator

OnEntityBuilt

OnEntityBuilt is called after a Planner successfully constructs a structure or deployable, at the point where the result is a live GameObject but before parenting and deployable setup run.

Signature
void OnEntityBuilt(Planner planner, UnityEngine.GameObject gameObject)

Parameters

Hook parameters
ParameterTypeDescription
plannerPlannerThe Planner entity that performed the build. Use it to read the building player via planner.GetOwnerPlayer(), the active item, or the planner's transform for position checks.
gameObjectUnityEngine.GameObjectThe freshly created GameObject for the placed structure or deployable. Convert it with GameObjectEx.ToBaseEntity(gameObject) to get a BaseEntity (BuildingBlock, base entity, deployable) whose position, grade, owner and flags you can inspect or modify.

Return value

The return value is ignored. Returning null, false or any object has no effect on Planner.DoBuild; the build completes normally. Use CanBuild or CanAffordToPlace if you need to block placement.

How it works

The hook fires once per successful placement from Planner.DoBuild. By the time it runs, construction checks have passed and the new GameObject exists; the game still has to resolve it into a BaseEntity, parent deployables to sockets and finish build bookkeeping. It does not fire when placement fails.

planner identifies the builder: GetOwnerPlayer() gives the BasePlayer who requested the build (may be null for NPC or automated builds). gameObject is the placed result. Convert it with GameObjectEx.ToBaseEntity(gameObject), then check the concrete type: BuildingBlock for walls, foundations and floors, other BaseEntity subclasses for deployables like boxes, doors or turrets.

The return value is ignored, so this is a notification hook: you can react to the placement but cannot cancel it. Because it fires before parenting completes, anything you do to the entity must tolerate that. Declaring BuildingBlock in place of UnityEngine.GameObject will not work here, because the game passes the GameObject, not the entity.

This hook fires only when a player places via a Planner and not from other construction paths, so treat it as a placement event rather than a general entity-spawn event.

Example plugin: Limit foundation placement per player

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

namespace Oxide.Plugins
{
    [Info("Foundation Build Limit", "RustPluginGenerator", "1.0.0")]
    [Description("Limits how many foundations each player can place and counts new builds via OnEntityBuilt.")]
    public class FoundationBuildLimit : RustPlugin
    {
        private const string PermBypass = "foundationbuildlimit.bypass";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Max foundations per player")]
            public int MaxFoundations = 3;

            [JsonProperty("Bypass permission")]
            public string BypassPermission = PermBypass;
        }

        private class StoredData
        {
            public Dictionary<string, int> Counts = new Dictionary<string, int>();
        }

        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);

        private void Init()
        {
            permission.RegisterPermission(PermBypass, this);
        }

        private void OnServerInitialized()
        {
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

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

        private object CanBuild(Planner planner, Construction construction, Construction.Target target)
        {
            var player = planner?.GetOwnerPlayer();
            if (player == null || planner.isTypeDeployable) return null;
            if (permission.UserHasPermission(player.UserIDString, config.BypassPermission)) return null;

            var prefab = construction?.fullName;
            if (prefab == null || !prefab.Contains("foundation")) return null;

            if (CountFoundations(player.userID) >= config.MaxFoundations)
            {
                ChatMessage(player, $"You have reached the {config.MaxFoundations} foundation limit.");
                return false;
            }
            return null;
        }

        private void OnEntityBuilt(Planner planner, GameObject gameObject)
        {
            if (gameObject == null) return;
            var player = planner?.GetOwnerPlayer();
            if (player == null) return;

            var entity = gameObject.ToBaseEntity();
            var block = entity as BuildingBlock;
            if (block == null) return;
            if (!block.ShortPrefabName.Contains("foundation")) return;

            if (data == null) return;
            string id = player.UserIDString;
            int current;
            data.Counts.TryGetValue(id, out current);
            data.Counts[id] = current + 1;
        }

        private int CountFoundations(ulong userId)
        {
            int count = 0;
            foreach (var net in BaseNetworkable.serverEntities)
            {
                var block = net as BuildingBlock;
                if (block == null) continue;
                if (block.OwnerID != userId) continue;
                if (!block.ShortPrefabName.Contains("foundation")) continue;
                count++;
            }
            return count;
        }

        private void ChatMessage(BasePlayer player, string text)
        {
            player.ChatMessage(text);
        }
    }
}

The plugin counts BuildingBlock entities at the Foundation grade per player and blocks new placements through CanBuild once the configured limit is reached. OnEntityBuilt fires for each placement, reads the builder with planner.GetOwnerPlayer(), converts the GameObject with GameObjectEx.ToBaseEntity, and if it is a foundation increments a per-user counter kept in data. A cooldown check in CanBuild walks BaseNetworkable.serverEntities to count existing foundations so the limit survives restarts without scanning on every build. Permission holders bypass the limit, and admins get a chat reply with the current count.

Common use cases

  • Log player construction for audit trails

    Record who placed which structure and where by converting the GameObject and reading ShortPrefabName and transform.position from the builder.

  • Enforce per-player base size limits

    Track placed foundations or walls per player and block new placement in CanBuild once the configured quota is hit.

  • Grant build rewards or XP

    Award a small currency or progression value each time a player finishes a placement, using planner.GetOwnerPlayer() as the recipient.

Pitfalls

  • Do not modify the GameObject recklessly

    Parenting and deployable setup still run after the hook. Killing or reparenting the entity here can leave dangling references or break socket logic. Signal via a timer with NextTick if you must change the entity.

  • Planner owner can be null

    Automated or NPC builds may call DoBuild with a planner whose GetOwnerPlayer() returns null. Always null-check before touching UserIDString or permission APIs.

  • Return value does not cancel

    This hook is notification-only. To prevent a placement, use CanBuild or CanAffordToPlace; adjusting the return here has no effect.

Plugin recipes that use OnEntityBuilt

Build a plugin with OnEntityBuilt

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

Prompt: Log player construction for audit trails using the OnEntityBuilt 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 OnEntityBuilt

Generate