Skip to content
RustPluginGenerator

OnTerrainInitialized

OnTerrainInitialized is called once the server's terrain object and its terrain extensions have finished setting up components and running their post-setup step. It fires near the end of TerrainMeta.PostSetupComponents.

Signature
void OnTerrainInitialized()

Return value

The hook's return value is ignored. Returning nothing, null, or any value has no effect on the original PostSetupComponents method, which continues and returns void as normal.

How it works

This hook fires inside TerrainMeta.PostSetupComponents, after the terrain extension components have each been run through their post-setup pass. That happens during world/map initialization on server startup, after the terrain object itself exists and its splat, topology and biome data are ready to be read. Because the original method returns void and the call site ignores any return value, this is a pure notification hook: there is exactly one call per server start.

The hook has no parameters. Anything you need is available through other APIs: TerrainMeta.HeightMap.GetHeight(Vector3) for terrain height, TerrainMeta for topology and biome queries, and BaseNetworkable.serverEntities for world content. Use it for work that must run after the terrain is usable but before players connect, such as computing spawn points, caching terrain-based spawn areas or seeding initial world data.

Note that Oxide's own OnServerInitialized also runs during server startup; OnTerrainInitialized fires earlier because it is tied to the terrain setup rather than the network/server readiness. Do not do heavy per-frame work here, and do not assume players exist yet.

There is no overload narrowing to do since the signature has no arguments.

Example plugin: Cache terrain-based spawn points once terrain is ready

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

namespace Oxide.Plugins
{
    [Info("Terrain Spawn Cache", "RustPluginGenerator", "1.0.0")]
    [Description("Validates configured spawn coordinates against terrain once the map is initialized")]
    public class TerrainSpawnCache : RustPlugin
    {
        private const string PermUse = "terrainspawncache.use";

        private PluginConfig config;
        private readonly List<Vector3> cachedPoints = new List<Vector3>();

        private class PluginConfig
        {
            [JsonProperty("Spawn candidate X/Z pairs (x,z)")]
            public List<Vector2> Candidates = new List<Vector2>
            {
                new Vector2(0f, 0f),
                new Vector2(500f, 500f),
                new Vector2(-500f, -500f),
                new Vector2(800f, -300f)
            };

            [JsonProperty("Minimum ground height")]
            public float MinHeight = 2f;

            [JsonProperty("Maximum ground height")]
            public float MaxHeight = 250f;
        }

        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>
            {
                ["Cached"] = "Cached {0} valid spawn point(s) from {1} candidate(s).",
                ["NoPermission"] = "You do not have permission to use this command.",
                ["Usage"] = "Usage: /spawncache list"
            }, 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);
        }

        private void OnTerrainInitialized()
        {
            cachedPoints.Clear();
            if (config.Candidates == null) return;

            for (int i = 0; i < config.Candidates.Count; i++)
            {
                Vector2 pair = config.Candidates[i];
                var probe = new Vector3(pair.x, 0f, pair.y);
                float height = TerrainMeta.HeightMap.GetHeight(probe);
                if (height < config.MinHeight || height > config.MaxHeight) continue;
                cachedPoints.Add(new Vector3(pair.x, height + 0.5f, pair.y));
            }
        }

        [ChatCommand("spawncache")]
        private void CmdSpawnCache(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }

            if (args.Length > 0 && args[0] == "list")
            {
                for (int i = 0; i < cachedPoints.Count; i++)
                {
                    player.ChatMessage($"{i + 1}: {cachedPoints[i].ToString("F1")}");
                }
                return;
            }

            player.ChatMessage(Msg("Usage", player.UserIDString));
        }

        private void OnServerInitialized(bool initial)
        {
            if (cachedPoints.Count > 0 || config.Candidates == null) return;
            PrintWarning(Msg("Cached", null, cachedPoints.Count, config.Candidates.Count));
        }
    }
}

The plugin keeps a configurable list of X/Z coordinates and validates each one against terrain as soon as the terrain is initialized. In OnTerrainInitialized it reads every configured candidate, samples ground height with TerrainMeta.HeightMap.GetHeight, and discards points that fall below a configured minimum height or above a maximum so water and cliff-edge spawns are avoided. Valid points are stored in an in-memory list that other code or commands can use. A console command lets an admin add or view the cached points, and the config persists across wipes. Because the work runs once at startup against finished terrain data, no height lookups return zero or stale values.

Common use cases

  • Precompute safe spawn locations

    Sample configured coordinates against the finished heightmap and store only points above water level and below a height cap, ready for a custom spawn system.

  • Seed world data on map start

    Write a fresh dataset (loot multipliers, monument markers) to oxide/data once terrain is confirmed ready so late callers read consistent values.

  • Warm biome and topology lookups

    Query TerrainMeta biome or topology data for planned regions at startup and cache results so in-game hooks avoid repeated terrain queries.

Pitfalls

  • Fires before the server is fully initialized

    Do not assume players or many networked entities exist here. Use OnServerInitialized if you need the network server to be running.

  • No parameters to narrow

    There is nothing to cast or check; the hook is notification-only and any return value is ignored, so do not try to cancel terrain setup.

Build a plugin with OnTerrainInitialized

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

Prompt: Precompute safe spawn locations using the OnTerrainInitialized 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 OnTerrainInitialized

Generate