How to make a Rust vehicle owner lock plugin (team sharing)
Adds per-vehicle ownership: a player can claim a vehicle with /claim, and only the owner and their team members can mount it. Ownership is enforced in the CanMountEntity hook and handed out when a vehicle is deployed.
On a busy server, anyone can hop into a parked MiniCopter, ModularCar or boat and drive it away or loot the fuel tank. There is no built-in ownership flag on vehicles, so the game will happily let a stranger mount a vehicle you spawned. This plugin stores a small ownership record keyed by the vehicle's network ID and blocks mounting for everyone except the owner and the owner's current team. The owner is recorded when a player deploys a mountable with OnItemDeployed, and /claim lets a player take an unowned vehicle they are looking at. CanMountEntity, which fires whenever a player tries to sit down on a mountable, consults the record and denies the mount for non-members by returning a non-null value. Data is saved through DataFileSystem so ownership survives restarts, and records are removed when the exact entity is killed via OnEntityKill.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("VehicleOwnerLock", "RustPluginGenerator", "1.0.0")]
[Description("Locks deployed or claimed vehicles to their owner and team.")]
public class VehicleOwnerLock : RustPlugin
{
private const string PermClaim = "vehicleownerlock.claim";
private const string DataFile = "VehicleOwnerLock";
private class PluginConfig
{
[JsonProperty("Claim range in meters")]
public float ClaimRange = 4f;
[JsonProperty("Allow team members to mount")]
public bool AllowTeam = true;
[JsonProperty("Auto-claim on deploy")]
public bool AutoClaimOnDeploy = true;
}
private class VehicleData
{
public string OwnerId;
public string OwnerName;
}
private PluginConfig config;
private Dictionary<ulong, VehicleData> data = new Dictionary<ulong, VehicleData>();
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>
{
["Locked"] = "This vehicle is locked to {0} and their team.",
["Claimed"] = "You claimed this vehicle. Only you and your team can mount it.",
["AlreadyOwned"] = "You already own this vehicle.",
["NotClaimable"] = "This vehicle belongs to {0}, it cannot be claimed.",
["NothingFound"] = "Look directly at a vehicle within {0} meters and try again.",
["Released"] = "You released this vehicle. Anyone can claim it now.",
["InfoOwned"] = "Owner: {0}. You {1} mount it.",
["InfoUnowned"] = "This vehicle is unowned.",
["AutoClaimed"] = "This vehicle is now locked to you and your team.",
["NoPermission"] = "You do not have permission to use this command.",
["Allowed"] = "may",
["Denied"] = "may not"
}, this);
}
private void Init()
{
permission.RegisterPermission(PermClaim, this);
}
private void OnServerInitialized()
{
data = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, VehicleData>>(DataFile)
?? new Dictionary<ulong, VehicleData>();
SaveData();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(DataFile, data);
}
private string Msg(string key, string userId = null, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
private object CanMountEntity(BasePlayer player, BaseMountable mountable)
{
if (player == null || mountable == null) return null;
if (player.IsAdmin) return null;
VehicleData record;
if (!data.TryGetValue(mountable.net.ID.Value, out record)) return null;
if (record.OwnerId == player.UserIDString) return null;
if (config.AllowTeam && player.currentTeam != 0)
{
var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
if (team != null && team.members.Contains(Convert.ToUInt64(record.OwnerId)))
return null;
}
player.ChatMessage(Msg("Locked", player.UserIDString, record.OwnerName));
return "Vehicle locked";
}
private void OnItemDeployed(Deployer deployer, BaseEntity entity)
{
if (!config.AutoClaimOnDeploy || deployer == null || entity == null) return;
var player = deployer.GetOwnerPlayer();
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermClaim)) return;
var mountable = entity as BaseMountable;
if (mountable == null) return;
ulong id = mountable.net.ID.Value;
if (data.ContainsKey(id)) return;
data[id] = new VehicleData { OwnerId = player.UserIDString, OwnerName = player.displayName };
SaveData();
player.ChatMessage(Msg("AutoClaimed", player.UserIDString));
}
private void OnEntityKill(BaseNetworkable entity)
{
if (entity == null) return;
if (data.Remove(entity.net.ID.Value)) SaveData();
}
[ChatCommand("claim")]
private void CmdClaim(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermClaim))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
var mountable = FindLookedAt(player);
if (mountable == null)
{
SendReply(player, Msg("NothingFound", player.UserIDString, config.ClaimRange));
return;
}
ulong id = mountable.net.ID.Value;
VehicleData record;
if (data.TryGetValue(id, out record))
{
if (record.OwnerId == player.UserIDString)
SendReply(player, Msg("AlreadyOwned", player.UserIDString));
else
SendReply(player, Msg("NotClaimable", player.UserIDString, record.OwnerName));
return;
}
data[id] = new VehicleData { OwnerId = player.UserIDString, OwnerName = player.displayName };
SaveData();
SendReply(player, Msg("Claimed", player.UserIDString));
}
[ChatCommand("vlock")]
private void CmdVlock(BasePlayer player, string command, string[] args)
{
if (player == null) return;
var mountable = FindLookedAt(player);
if (mountable == null)
{
SendReply(player, Msg("NothingFound", player.UserIDString, config.ClaimRange));
return;
}
string sub = args != null && args.Length > 0 ? args[0].ToLowerInvariant() : "info";
if (sub == "release")
{
if (!permission.UserHasPermission(player.UserIDString, PermClaim))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
VehicleData record;
if (!data.TryGetValue(mountable.net.ID.Value, out record) || record.OwnerId != player.UserIDString)
{
SendReply(player, Msg("NotClaimable", player.UserIDString, record?.OwnerName ?? "someone else"));
return;
}
data.Remove(mountable.net.ID.Value);
SaveData();
SendReply(player, Msg("Released", player.UserIDString));
return;
}
VehicleData owned;
if (!data.TryGetValue(mountable.net.ID.Value, out owned))
{
SendReply(player, Msg("InfoUnowned", player.UserIDString));
return;
}
bool allowed = owned.OwnerId == player.UserIDString || (config.AllowTeam && IsTeamMember(player, owned));
SendReply(player, Msg("InfoOwned", player.UserIDString, owned.OwnerName, Msg(allowed ? "Allowed" : "Denied", player.UserIDString)));
}
private bool IsTeamMember(BasePlayer player, VehicleData record)
{
if (player.currentTeam == 0) return false;
var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
return team != null && team.members.Contains(Convert.ToUInt64(record.OwnerId));
}
private BaseMountable FindLookedAt(BasePlayer player)
{
RaycastHit hit;
if (!Physics.Raycast(player.eyes.position, player.eyes.HeadForward(), out hit, config.ClaimRange, 1237003025))
return null;
var entity = hit.GetEntity();
while (entity != null)
{
var mountable = entity as BaseMountable;
if (mountable != null) return mountable;
entity = entity.GetParentEntity();
}
return null;
}
}
}Hooks used: OnServerInitialized, CanMountEntity, OnItemDeployed, OnEntityKill
Config file
Created automatically on first load at oxide/config/VehicleOwnerLock.json.
{
"Claim range in meters": 4,
"Allow team members to mount": true,
"Auto-claim on deploy": true
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/claim | Claim the vehicle you are looking at if it has no owner yet. | vehicleownerlock.claim |
/vlock info | Show the owner of the vehicle you are looking at and whether you may mount it. | Everyone |
/vlock release | Remove your ownership from the vehicle you are looking at, making it claimable again. | vehicleownerlock.claim |
| Permission | Grants |
|---|---|
vehicleownerlock.claim | Allows claiming an unowned vehicle, auto-claiming on deploy, and releasing ownership of your own vehicle. |
How the code works
Store ownership by network ID
Each vehicle is identified by
net.ID.Value, aulongthat is stable for the entity's lifetime. The plugin keeps aDictionary<ulong, VehicleData>in theVehicleOwnerLockdata file viaInterface.Oxide.DataFileSystem.VehicleDataholds the owner'sUserIDStringin theOwnerIdfield plusOwnerNamefor chat messages. The dictionary is loaded inOnServerInitializedand written whenever it changes throughSaveData, so ownership survives restarts without touching entity save data.Deny mounts in CanMountEntity
The hook
object CanMountEntity(BasePlayer player, BaseMountable mountable)runs before a seat is taken. The plugin looks upmountable.net.ID.Value, and if a record exists and neitherplayer.UserIDStringmatchesOwnerIdnor the player is on the owner's team, it sends a lang message and returns a non-null string, which blocks the mount. Returningnulllets the mount proceed. Admins skip the check. Team membership comes fromRelationshipManager.ServerInstance.FindTeam(player.currentTeam), so removing a member from the team revokes access automatically.Claim an unowned vehicle with /claim
/claimcasts a ray fromplayer.eyes.positionalongeyes.HeadForward()for the configured range, then walks the hit entity and its parents withGetParentEntity()looking for aBaseMountable. If the target has no record it is claimed and saved. If you already own it, the command says so. The permissionvehicleownerlock.claimis checked first, so only permitted players can claim.Record ownership when a mountable is deployed
OnItemDeployed(Deployer deployer, BaseEntity entity)fires when a player places an item that turns into an entity. The plugin gets the deployer withdeployer.GetOwnerPlayer(), checks the claim permission, casts the entity toBaseMountable, and if no record exists writes the player as the new owner. The player sees theAutoClaimedlang message viaChatMessage. This removes the need for a manual/claimafter every spawn whenAuto-claim on deployis enabled.Clean up destroyed vehicles
In
OnEntityKill(BaseNetworkable entity)the plugin removes any record whose key matches the dying entity's network ID. Without this the data file grows with dead copters and cars. The hook is cheap: one dictionary lookup and a save only when a key was actually removed (data.Removereturnstrue).Read the record back in /vlock info
/vlock infouses the sameFindLookedAthelper and prints the owner name fromVehicleDataalong with whether you may mount it, built from theInfoOwnedmessage with theAllowedandDeniedlang keys. If no record exists it reports the vehicle as unowned. This is useful for admins diagnosing complaints and for players who want to know who they must ask before a copter can be borrowed.
Install it on your server
Save VehicleOwnerLock.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/VehicleOwnerLock.json(Carbon:carbon/configs/VehicleOwnerLock.json). After editing, reload it:oxide.reload VehicleOwnerLock c.reload VehicleOwnerLock # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default vehicleownerlock.claim c.grant group default vehicleownerlock.claim # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Short lockout on last dismount
Keep vehicles mountable by the owner but reserve them for a few minutes after they last use them.
Generate this versionAllow team leaders to transfer ownership
Let a team leader hand a locked vehicle to another team member with a command.
Generate this versionIgnore unowned vehicles after a timer
Automatically claim abandoned vehicles for whoever mounts them after a configurable idle time.
Generate this version
FAQ
Does it work on Carbon?
Yes. Carbon loads the same Oxide plugin file, and the hooks used here are standard Rust hooks. Drop the compiled plugin in the same plugins folder and the config is generated on first load.
What happens if the owner leaves the team?
Team checks happen live in CanMountEntity through RelationshipManager.ServerInstance.FindTeam. Once a player is no longer in the owner's team, their mount is denied without any plugin action.
Can I make vehicles claimable again?
Yes. The owner can run /vlock release while looking at the vehicle. There is no admin bypass in /vlock release; only the recorded owner can release it.
Which vehicles are covered?
Every BaseMountable. That includes MiniCopter, ModularCar, boats and horse saddles because they all derive from mountables. Deployed items that turn into mountables are auto-claimed via OnItemDeployed when the deployer has the claim permission.
How do I stop owner locking for guests?
Do not grant vehicleownerlock.claim to the group; the deploy hook and /claim check the permission before claiming or auto-claiming, so unpermitted players cannot create new ownership records. Existing owned vehicles still block them in CanMountEntity unless they are admins.
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
- Car spawn commandAdds 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โฆOnEntityKill
- 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
Last updated Oct 26, 2026.
Customize vehicle owner lock
Customize