Skip to content
RustPluginGenerator

OnPayForPlacement

OnPayForPlacement is called right before a planner charges the player for a placement. Returning a non-null value skips the payment step entirely, effectively making the placement free (or blocking it if you also handle that elsewhere).

Signature
object OnPayForPlacement(BasePlayer player, Planner planner, Construction component)

Parameters

Hook parameters
ParameterTypeDescription
playerBasePlayerThe BasePlayer who is trying to place a construction. Null-check before using; on server-driven placement paths it should always be set.
plannerPlannerThe planner item entity doing the placement. Usually a Planner for building blocks and deployables, or a WallpaperPlanner when applying wallpaper. Declare the matching concrete type to only receive that variant.
componentConstructionThe Construction being placed, which carries the prefab and grade info. Useful if you want to inspect the prefab path or the building grade before deciding whether to allow the placement.

Return value

Return null to let the game charge the player normally. Return any non-null value to skip the charge entirely; the player places the construction without losing resources.

How it works

The hook fires inside Planner.PayForPlacement and WallpaperPlanner.PayForPlacement, which run when the player confirms a building or wallpaper placement. Planner charges the construction's price (wood, stone, etc.), while WallpaperPlanner deducts wallpaper from the player's inventory; both wrap the charge behind this hook. The player argument is the builder, planner is the planner entity involved, and component is the Construction being placed.

Because the two call sites pass different planner types, you can declare Planner and WallpaperPlanner overloads separately, or keep a single BaseEntity-typed parameter to catch both. Declaring Planner narrows the call to building placement only; a WallpaperPlanner placement then won't reach that method.

The check happens before any inventory mutation, so returning non-null here means the player keeps their resources. Creative mode and tutorial handling in the vanilla Planner path still runs, since this hook is evaluated first. Use it to implement free-build zones, admin overrides, donation perks, or to refuse placement by combining the skip with your own validation and player feedback.

Other signatures

Oxide also calls OnPayForPlacement from other places in the game with these parameter types. Declare the overload you need, or several:

  • object OnPayForPlacement(BasePlayer player, WallpaperPlanner wallpaperPlanner, Construction component)

Example plugin: Free building inside marked zones

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

namespace Oxide.Plugins
{
    [Info("Free Build Zones", "RustPluginGenerator", "1.0.0")]
    [Description("Skips planner resource cost for constructions placed inside configured zones")]
    public class FreeBuildZones : RustPlugin
    {
        private const string PermUse = "freebuildzones.use";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Zone radius")]
            public float Radius = 25f;

            [JsonProperty("Zone centers (x,y,z)")]
            public List<ZonePoint> Zones = new List<ZonePoint>();
        }

        private class ZonePoint
        {
            [JsonProperty("X")]
            public float X;
            [JsonProperty("Y")]
            public float Y;
            [JsonProperty("Z")]
            public float Z;
        }

        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>
            {
                ["ZoneAdded"] = "Added a free-build zone at your position.",
                ["NoZones"] = "There are no free-build zones configured.",
                ["ListHeader"] = "Free-build zones: {0}",
                ["ListEntry"] = "#{0} at {1}",
                ["PlacedFree"] = "Materials not charged: you are inside a free-build zone."
            }, 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);
        }

        [ChatCommand("freezone")]
        private void CmdFreeZone(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!player.IsAdmin) { SendReply(player, "Admin only."); return; }

            if (args == null || args.Length == 0)
            {
                if (config.Zones.Count == 0) { SendReply(player, Msg("NoZones", player.UserIDString)); return; }
                SendReply(player, Msg("ListHeader", player.UserIDString, config.Zones.Count));
                for (int i = 0; i < config.Zones.Count; i++)
                {
                    var z = config.Zones[i];
                    SendReply(player, Msg("ListEntry", player.UserIDString, i, $"({z.X:0}, {z.Y:0}, {z.Z:0})"));
                }
                return;
            }

            if (args[0].ToLower() == "add")
            {
                Vector3 pos = player.transform.position;
                config.Zones.Add(new ZonePoint { X = pos.x, Y = pos.y, Z = pos.z });
                SaveConfig();
                SendReply(player, Msg("ZoneAdded", player.UserIDString));
            }
        }

        private bool InFreeZone(Vector3 pos)
        {
            float r = config.Radius;
            float r2 = r * r;
            foreach (var z in config.Zones)
            {
                var center = new Vector3(z.X, z.Y, z.Z);
                if ((pos - center).sqrMagnitude <= r2) return true;
            }
            return false;
        }

        private object HandlePay(BasePlayer player, Construction component)
        {
            if (player == null || component == null) return null;
            if (!permission.UserHasPermission(player.UserIDString, PermUse)) return null;
            if (!InFreeZone(component.transform.position)) return null;

            SendReply(player, Msg("PlacedFree", player.UserIDString));
            return true;
        }

        private object OnPayForPlacement(BasePlayer player, Planner planner, Construction component)
            => HandlePay(player, component);

        private object OnPayForPlacement(BasePlayer player, WallpaperPlanner planner, Construction component)
            => HandlePay(player, component);
    }
}

The plugin keeps a small list of rectangular zones in its config. When a player tries to place a construction, OnPayForPlacement checks whether their position falls inside any configured zone. If it does, and the player has the freebuildzones.use permission, the hook returns true so the game skips the resource charge. Otherwise it returns null and normal pricing applies. A chat command lets admins manage the nearby zone, and a helper command shows the player what is about to be charged. The WallpaperPlanner overload is handled the same way so wallpaper stays free inside the zone too.

Common use cases

  • Creative-style building areas

    Let staff or trusted players build in a designated plot without consuming materials, while the rest of the map stays normal.

  • VIP free-build perk

    Gate free placement behind a permission so only donators skip the charge; return null for everyone else.

  • Blocking placement with feedback

    If a player is not allowed to build at all, return a non-null value to prevent the charge and send them a message explaining the rule.

Pitfalls

  • Returning non-null skips payment but not placement

    The hook only controls the charge. The construction still spawns; if you want to actually block building, combine this with a construction or building hook.

  • WallpaperPlanner overload

    Declaring Planner alone misses wallpaper placements. Add the WallpaperPlanner overload if wallpaper should follow the same rule.

  • Creative mode already bypasses payment

    The vanilla Planner path returns early for creative players with free build enabled, so your free zone logic only affects normal players.

Build a plugin with OnPayForPlacement

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

Prompt: Creative-style building areas using the OnPayForPlacement 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 OnPayForPlacement

Generate