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.
void OnEntityBuilt(Planner planner, UnityEngine.GameObject gameObject)Parameters
| Parameter | Type | Description |
|---|---|---|
planner | Planner | The 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. |
gameObject | UnityEngine.GameObject | The 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
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
ShortPrefabNameandtransform.positionfrom the builder.Enforce per-player base size limits
Track placed foundations or walls per player and block new placement in
CanBuildonce 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
NextTickif you must change the entity.Planner owner can be null
Automated or NPC builds may call
DoBuildwith a planner whoseGetOwnerPlayer()returns null. Always null-check before touchingUserIDStringor permission APIs.Return value does not cancel
This hook is notification-only. To prevent a placement, use
CanBuildorCanAffordToPlace; adjusting the return here has no effect.
Plugin recipes that use OnEntityBuilt
- How to make a Rust auto code lock plugin (personal codes)Automatically attaches a code lock with the player's saved personal code to every door or box they place, consuming a code lock from their inventory. Uses OnEntityBuilt plus a stored per-player PIN.
- How to make a Rust auto upgrade on build plugin (grade per player)Lets each player pick a building grade with /up 1-4, then upgrades every building block they place to that grade using resources taken from their inventory. It uses OnEntityBuilt and the BuildingBlock grade system.
- How to make a Rust deployable limits plugin (per-prefab caps with VIP tiers)Caps how many of each deployable (by short prefab name) a player can own, with per-permission-group limits, using OnEntityBuilt to enforce, CanBuild to block placement early, and OnEntityKill plus a startup scan of BaseNetworkable.serverEntities to keep counts accurate.
Build a plugin with OnEntityBuilt
Describe what should happen and get a compiled plugin that uses this hook.
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