Skip to content
RustPluginGenerator

How to make a Rust car spawn plugin (Oxide/Carbon)

Adds a /car command that spawns a modular car with configured modules in front of the player, enforcing a permission, a spawn cooldown and a strict one-car-per-player limit. It tracks spawned cars through OnEntityKill so a player can get a new car only after the old one is destroyed.

Handing players a vehicle usually means admins manually spawning prefabs, or players farming for hours to build a car at a vehicle lift. A spawn command solves that, but the naive version leaks cars: players spam the command, leave abandoned chassis across the map, and nobody cleans them up. This plugin keeps a per-player registry of spawned cars and refuses a new spawn while one is still alive. The /car command checks a permission, a building-blocked rule, a cooldown stored in oxide/data, and the registry, then creates the chassis from GameManager.server.CreateEntity. The Modules list in the config is not used by this version; only the chassis prefab is spawned. OnEntityKill removes destroyed cars from the registry immediately, and the one-car check also drops stale entries, so the rule does not leave players locked out after their car is blown up. The chassis is placed a few metres in front of the player at eye level, raised to terrain height only if it would otherwise be below ground.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("Car Spawn", "RustPluginGenerator", "1.0.0")]
    [Description("Adds a /car command that spawns a modular car in front of the player with a cooldown and a one-car limit.")]
    public class CarSpawn : RustPlugin
    {
        private const string PermUse = "carspawn.use";

        private class PluginConfig
        {
            [JsonProperty("Chassis prefab")]
            public string ChassisPrefab = "assets/content/vehicles/modularcar/2module_car_spawned.entity.prefab";

            [JsonProperty("Modules")]
            public List<string> Modules = new List<string>
            {
                "assets/content/vehicles/modularcar/models/components/engine.entity.prefab",
                "assets/content/vehicles/modularcar/models/components/seat.entity.prefab"
            };

            [JsonProperty("Spawn distance")]
            public float SpawnDistance = 4f;

            [JsonProperty("Cooldown seconds")]
            public float CooldownSeconds = 300f;

            [JsonProperty("Allow spawning while building blocked")]
            public bool AllowBuildingBlocked = false;
        }

        private class StoredData
        {
            [JsonProperty("Spawned cars")]
            public Dictionary<ulong, ulong> Cars = new Dictionary<ulong, ulong>();

            [JsonProperty("Cooldowns")]
            public Dictionary<ulong, DateTime> Cooldowns = new Dictionary<ulong, DateTime>();
        }

        private PluginConfig config;
        private StoredData data;

        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>
            {
                ["NoPermission"] = "You do not have permission to use this command.",
                ["Cooldown"] = "You must wait {0} more seconds before spawning another car.",
                ["AlreadyHaveCar"] = "You already have a spawned car. Destroy it first.",
                ["BuildingBlocked"] = "You cannot spawn a car while building is blocked.",
                ["SpawnFailed"] = "The car could not be spawned. Check the server configuration.",
                ["Spawned"] = "Your car has been spawned in front of you."
            }, 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 Loaded()
        {
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

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

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

            if (!config.AllowBuildingBlocked && player.IsBuildingBlocked())
            {
                player.ChatMessage(Msg("BuildingBlocked", player.UserIDString));
                return;
            }

            DateTime last;
            if (data.Cooldowns.TryGetValue(player.userID, out last))
            {
                var remaining = config.CooldownSeconds - (float)(DateTime.UtcNow - last).TotalSeconds;
                if (remaining > 0f)
                {
                    player.ChatMessage(Msg("Cooldown", player.UserIDString, Mathf.CeilToInt(remaining)));
                    return;
                }
            }

            if (HasActiveCar(player.userID))
            {
                player.ChatMessage(Msg("AlreadyHaveCar", player.UserIDString));
                return;
            }

            var car = SpawnCar(player);
            if (car == null)
            {
                player.ChatMessage(Msg("SpawnFailed", player.UserIDString));
                return;
            }

            data.Cars[player.userID] = car.net.ID.Value;
            data.Cooldowns[player.userID] = DateTime.UtcNow;
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            player.ChatMessage(Msg("Spawned", player.UserIDString));
        }

        private bool HasActiveCar(ulong userId)
        {
            ulong carId;
            if (!data.Cars.TryGetValue(userId, out carId)) return false;

            var entity = BaseNetworkable.serverEntities.Find(new NetworkableId(carId)) as ModularCar;
            if (entity == null || entity.IsDestroyed)
            {
                data.Cars.Remove(userId);
                return false;
            }
            return true;
        }

        private ModularCar SpawnCar(BasePlayer player)
        {
            var position = player.eyes.position + player.eyes.HeadForward() * config.SpawnDistance;
            var ground = TerrainMeta.HeightMap.GetHeight(position);
            if (position.y < ground) position.y = ground + 0.5f;

            var entity = GameManager.server.CreateEntity(config.ChassisPrefab, position, Quaternion.identity);
            if (entity == null) return null;

            var car = entity as ModularCar;
            if (car == null)
            {
                entity.Kill();
                return null;
            }

            car.OwnerID = player.userID;
            car.Spawn();
            return car;
        }

        private object OnEntityKill(BaseNetworkable entity)
        {
            if (entity == null || data == null || data.Cars.Count == 0) return null;

            var id = entity.net.ID.Value;
            ulong owner = 0;
            bool found = false;

            foreach (var entry in data.Cars)
            {
                if (entry.Value == id)
                {
                    owner = entry.Key;
                    found = true;
                    break;
                }
            }

            if (found)
            {
                data.Cars.Remove(owner);
                Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            }

            return null;
        }
    }
}

Hooks used: OnEntityKill

Config file

Created automatically on first load at oxide/config/CarSpawn.json.

CarSpawn.json
{
  "Chassis prefab": "assets/content/vehicles/modularcar/2module_car_spawned.entity.prefab",
  "Modules": [
    "assets/content/vehicles/modularcar/models/components/engine.entity.prefab",
    "assets/content/vehicles/modularcar/models/components/seat.entity.prefab"
  ],
  "Spawn distance": 4,
  "Cooldown seconds": 300,
  "Allow spawning while building blocked": false
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/carSpawns a modular car with the configured modules in front of you if you have permission, no active car and no cooldown.carspawn.use
Permissions
PermissionGrants
carspawn.useGrants access to the /car command.

How the code works

  1. Configuring the chassis and modules

    PluginConfig holds a Chassis Prefab string (default assets/content/vehicles/modularcar/2module_car_spawned.entity.prefab), a Modules list, a Spawn Distance float, a Cooldown Seconds float and an Allow Building Blocked bool. LoadDefaultConfig assigns a fresh instance and the standard LoadConfig/SaveConfig overrides persist it. The Modules list is present in the config but the spawn code never reads it, so changing the chassis prefab does not require editing the module list.

  2. Creating the vehicle entity

    The spawn path calls GameManager.server.CreateEntity(config.ChassisPrefab, position, Quaternion.identity). The returned entity is null when the prefab string is wrong or the entity is not a ModularCar, so the command warns the player instead of throwing. The entity is cast to ModularCar, marked with the owner's OwnerID, spawned with Spawn(), and its net ID is stored in data.Cars.

  3. Placing the car in front of the player

    The spawn position is computed from player.eyes.position + player.eyes.HeadForward() * config.SpawnDistance. The plugin then calls TerrainMeta.HeightMap.GetHeight(position) and, only if the position is below that height, raises it to the terrain height plus a small offset. If the player is inside a base the car may still overlap a wall; the distance is configurable and defaults to a value outside melee range.

  4. Enforcing one car per player and the cooldown

    data.Cars is a Dictionary<ulong, ulong> mapping a player's Steam ID to their car's net ID. Before spawning, the command resolves the stored net ID through BaseNetworkable.serverEntities.Find(new NetworkableId(id)); if the entity is missing or destroyed, the entry is removed, and if it still exists as a ModularCar, the spawn is refused. Cooldowns live in a separate dictionary and are compared against DateTime.UtcNow. Both dictionaries are written through Interface.Oxide.DataFileSystem on every successful spawn and on removal of a stale entry.

  5. Cleaning up with OnEntityKill

    When any entity is killed, OnEntityKill(BaseNetworkable entity) fires. The plugin looks up the net ID in data.Cars; if it matches, the entry is removed and the data file is written. The registry also shrinks when the one-car check finds a missing or destroyed entity. Players who blow up their own car or lose it to another player are free to spawn again immediately. Return null from the hook to keep default behaviour.

  6. Messages and permissions

    All responses go through the language dictionary: NoPermission, Cooldown (with the remaining seconds), AlreadyHaveCar, BuildingBlocked, SpawnFailed and Spawned. Init registers carspawn.use, and the chat command checks it with permission.UserHasPermission. Unload writes the data one final time so state survives a plugin reload.

Install it on your server

  1. Save CarSpawn.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. Upload it to the plugins folder

    Oxide: oxide/plugins/. Carbon: carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/CarSpawn.json (Carbon: carbon/configs/CarSpawn.json). After editing, reload it:oxide.reload CarSpawn c.reload CarSpawn # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default carspawn.use c.grant group default carspawn.use # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

  • Command to recall a player's car

    Adds a /car recall command that teleports the player's stored car to them.

    Generate this version
  • Scrap cost per spawn

    Charges scrap from the player's inventory before the car spawns.

    Generate this version
  • Timed auto-despawn

    Destroys cars after a configurable lifetime so the map does not fill with empty chassis.

    Generate this version

FAQ

Which prefab should I use for the chassis?

The default is the two-module spawned chassis at assets/content/vehicles/modularcar/2module_car_spawned.entity.prefab. The Modules list in the config is not used by the spawn code, so switching to a three or four module chassis only requires changing the Chassis prefab string.

Why can a player not spawn a new car after losing theirs?

The one-car check looks up the stored net ID with BaseNetworkable.serverEntities.Find. If the entity is missing or destroyed, the plugin removes the entry and allows the spawn, so a lost car should not lock the player out. If an entry ever goes stale, deleting oxide/data/CarSpawn.json clears all registries.

Does the cooldown reset on restart?

No. Cooldowns are stored as UTC timestamps in the same data file and reloaded in Loaded. A player who triggered /car just before the restart still waits out the remaining time.

How do I install it on Oxide or Carbon?

Drop the compiled .cs file into oxide/plugins on an Oxide server, or carbon/plugins on Carbon. Carbon loads the same Oxide plugin file. Grant carspawn.use with oxide.grant user <name> carspawn.use and the default config is written on first load.

Customize it for your server

Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.

Prompt: Create a Rust Oxide plugin with a /car command that spawns a configurable modular car chassis with modules in front of the player, gated by the carspawn.use permission, a configurable cooldown and a one-car-per-player limit tracked through OnEntityKill.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize car spawn command

Customize