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
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.
{
"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
| Command | What it does | Permission |
|---|---|---|
/car | Spawns a modular car with the configured modules in front of you if you have permission, no active car and no cooldown. | carspawn.use |
| Permission | Grants |
|---|---|
carspawn.use | Grants access to the /car command. |
How the code works
Configuring the chassis and modules
PluginConfigholds aChassis Prefabstring (defaultassets/content/vehicles/modularcar/2module_car_spawned.entity.prefab), aModuleslist, aSpawn Distancefloat, aCooldown Secondsfloat and anAllow Building Blockedbool.LoadDefaultConfigassigns a fresh instance and the standardLoadConfig/SaveConfigoverrides persist it. TheModuleslist is present in the config but the spawn code never reads it, so changing the chassis prefab does not require editing the module list.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 aModularCar, so the command warns the player instead of throwing. The entity is cast toModularCar, marked with the owner'sOwnerID, spawned withSpawn(), and its net ID is stored indata.Cars.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 callsTerrainMeta.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.Enforcing one car per player and the cooldown
data.Carsis aDictionary<ulong, ulong>mapping a player's Steam ID to their car's net ID. Before spawning, the command resolves the stored net ID throughBaseNetworkable.serverEntities.Find(new NetworkableId(id)); if the entity is missing or destroyed, the entry is removed, and if it still exists as aModularCar, the spawn is refused. Cooldowns live in a separate dictionary and are compared againstDateTime.UtcNow. Both dictionaries are written throughInterface.Oxide.DataFileSystemon every successful spawn and on removal of a stale entry.Cleaning up with OnEntityKill
When any entity is killed,
OnEntityKill(BaseNetworkable entity)fires. The plugin looks up the net ID indata.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.Messages and permissions
All responses go through the language dictionary:
NoPermission,Cooldown(with the remaining seconds),AlreadyHaveCar,BuildingBlocked,SpawnFailedandSpawned.Initregisterscarspawn.use, and the chat command checks it withpermission.UserHasPermission.Unloadwrites the data one final time so state survives a plugin reload.
Install it on your server
Save CarSpawn.cs
Keep the file name exactly as the class name. Oxide matches them.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.Edit the config if you want
On first load the default config is written tooxide/config/CarSpawn.json(Carbon:carbon/configs/CarSpawn.json). After editing, reload it:oxide.reload CarSpawn c.reload CarSpawn # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip: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 versionScrap cost per spawn
Charges scrap from the player's inventory before the car spawns.
Generate this versionTimed 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.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More vehicles plugins
- Horse spawn commandAdds a /horse command that spawns a ridable horse beside the player, tracks one horse per player, removes that player's previous horse and enforcesโฆOnServerInitializedOnEntityKill
- No vehicle decayScales or disables decay damage on minicopters, boats, horses and modular cars per vehicle type, optionally only while inside a tool cupboard radius.OnEntityTakeDamage
- Unlimited vehicle fuelStops fuel drain on vehicles driven by players with a permission, limited to the vehicle types listed in config.OnFuelConsume
Last updated Oct 26, 2026.
Customize car spawn command
Customize