VehicleManager — Vehicle Spawning and Physics
The VehicleManager is the server-authoritative manager responsible for the complete lifecycle of all InteractableVehicle instances in an Unturned level. It owns vehicle spawning and despawn, fuel and damage sync, passenger management, tire damage, lock and skin state, the respawn timer system, and the batched state replication protocol that keeps clients synchronized with vehicle positions, rotations, velocities, and suspension states. At 3661 lines in Unturned/Managers/VehicleManager.cs, it is one of the largest manager classes in the SDK.
VehicleManager extends SteamCaller and follows the same static-delegate pattern as the other runtime managers: a static manager field, a public static instance accessor, and all public API methods are static. The manager maintains a single List<InteractableVehicle> _vehicles that holds every spawned vehicle in the level, and a monotonically incrementing highestInstanceID counter that assigns unique network instance IDs.
Source file:
Unturned/Managers/VehicleManager.cs— 3661 lines. This article references API surface, event delegates, and internal implementation visible in the U3 SDK.
Who this article is for
This article is written for plugin and module developers who need to spawn, damage, repair, or manage vehicles programmatically. It presupposes familiarity with the SteamCaller network transport layer, NetId ownership, and the Unturned asset system. If you are new to the SDK manager architecture, read ItemManager — Item Spawning first.
What you'll learn
- The vehicle spawn/despawn lifecycle, including
spawnVehicleInternalandSpawnVehicleV3 - The fuel, health, battery, and tire damage model
- Passenger enter/exit/swap seat management
- The respawn timer and respawn queue system
- The vehicle state replication protocol: batched position/rotation/velocity sync with sequenced packets
- Tire and wheel suspension replication
- Lock, skin, headlight, siren, and horn state synchronization
- Plugin hook points: damage, repair, enter, exit, lockpick, carjack, siphon
Vehicle list and instance ID allocation
The manager holds all vehicles in a static list:
csharp
private static List<InteractableVehicle> _vehicles;
public static List<InteractableVehicle> vehicles => _vehicles;Every vehicle receives a unique instance ID at spawn time. The counter is never decremented; IDs are unique for the lifetime of the server process.
csharp
private static uint highestInstanceID;
private static uint allocateInstanceID()
{
return ++highestInstanceID;
}The findVehicleByNetInstanceID method performs a linear scan across the vehicles list. At the scale of most Unturned levels (hundreds of vehicles, not thousands) this is acceptable, but plugin developers working with large vehicle counts should cache instance ID lookups.
Spawn limits by level size
The maxInstances property reads from Provider.modeConfigData.Vehicles.Max_Instances_* keyed by ELevelSize:
| Level Size | Max Instances |
|---|---|
| TINY | Config value |
| SMALL | Config value |
| MEDIUM | Config value |
| LARGE | Config value |
| INSANE | Config value |
Vehicles that exceed the configured maximum are not prevented from spawning by the manager itself — the limit is advisory and enforced by the respawn system, which skips spawning new vehicles when the count is at the cap.
Spawning vehicles
spawnVehicleInternal — The internal spawn pipeline
The spawnVehicleInternal method is the common entry point called by the public spawnVehicleV2 and spawnLockedVehicleForPlayerV2 overloads. It resolves the asset (handling VehicleRedirectorAsset indirection), determines paint color (from the caller or from the redirector), determines lock state (locked if an owner is specified), and delegates to SpawnVehicleV3.
csharp
internal static InteractableVehicle spawnVehicleInternal(Asset asset, Vector3 point,
Quaternion angle, CSteamID owner, CSteamID groupId, Color32? preferredColor)The redirector resolution logic:
- If the passed asset is a
VehicleRedirectorAsset, resolve itsTargetVehicleGUID. - If no
preferredColorwas provided but the redirector has aSpawnPaintColor, use that. - Otherwise fall through to the caller's color or zero.
Lock state is determined by whether the owner is CSteamID.Nil: if an owner is specified, the vehicle spawns locked and the group ID is set.
SpawnVehicleV3 — Full-parameter spawn
SpawnVehicleV3 is the most complete spawn method. It accepts every configurable vehicle parameter:
csharp
public static InteractableVehicle SpawnVehicleV3(VehicleAsset asset, ushort skinID,
ushort mythicID, float roadPosition, Vector3 point, Quaternion angle, bool sirens,
bool blimp, bool headlights, bool taillights, ushort fuel, ushort health,
ushort batteryCharge, CSteamID owner, CSteamID group, bool locked, byte[][] turrets,
byte tireAliveMask, Color32 paintColor)The method:
- Claims a block of
NetIds fromNetIdRegistry.ClaimBlock. - Calls
manager.addVehicleto instantiate the prefab and configure it. - Sends the full vehicle state to all remote clients via
SendSingleVehicle.Invoke. - Calls
vehicle.NotifyFirstSpawned()to trigger the spawned event.
The batteryCharge parameter has three modes:
0— spawn without a batteryushort.MaxValue— randomly spawn a battery per asset configuration- Any other value — force a specific battery charge
Tire alive mask randomization
csharp
public static byte getVehicleRandomTireAliveMask(VehicleAsset asset)When asset.canTiresBeDamaged is true, each of the 8 bit positions is randomly set to 1 with probability Provider.modeConfigData.Vehicles.Has_Tire_Chance. When tires cannot be damaged, the mask is byte.MaxValue (all tires present).
Damage and repair
vehicle damage pipeline
csharp
public static void damage(InteractableVehicle vehicle, float damage, float times,
bool canRepair, CSteamID instigatorSteamID, EDamageOrigin damageOrigin)The damage pipeline:
- Null-check vehicle and asset. Return silently on null.
- Check invulnerability flags: if the vehicle is not vulnerable to damage, explosions, or environment, log an error and return.
- Apply the config-defined
Armor_Multipliertotimes. - Calculate
totalDamage = (ushort)(damage * times). - Fire
onDamageVehicleRequesteddelegate. Plugins can modifytotalDamageandcanRepair, or cancel viashouldAllow. - If allowed and
totalDamage >= 1, callvehicle.askDamage(totalDamage, canRepair).
Tire damage
csharp
public static void damageTire(InteractableVehicle vehicle, int tireIndex,
CSteamID instigatorSteamID, EDamageOrigin damageOrigin)Tire damage fires onDamageTireRequested and, if allowed, calls vehicle.askDamageTire(tireIndex).
Repair
csharp
public static void repair(InteractableVehicle vehicle, float damage, float times,
CSteamID instigatorSteamID)The repair pipeline:
- Check vehicle is not exploded and not already fully repaired.
- Fire
onRepairVehicleRequested— plugins can modify the heal amount or cancel. - Call
vehicle.askRepair(amount).
Fuel system
Fuel is synchronized through sendVehicleFuel which invokes SendVehicleFuel to all clients. The client-side ReceiveVehicleFuel updates the fuel gauge on the client copy of the vehicle.
The siphonFromVehicle method allows players to extract fuel:
csharp
public static ushort siphonFromVehicle(InteractableVehicle vehicle,
Player instigatingPlayer, ushort desiredAmount)It fires onSiphonVehicleRequested for plugin interception, clamps the desired amount to available fuel, calls vehicle.askBurnFuel, and invokes sendVehicleFuel to broadcast the updated fuel level.
Passenger management
Enter vehicle
The enterVehicle static method sends an enter request to the server:
csharp
public static void enterVehicle(InteractableVehicle vehicle)It packs the vehicle's physics profile hash and engine type into the SendEnterVehicleRequest RPC. The server validates the request and, if accepted, broadcasts SendEnterVehicle which calls vehicle.addPlayer(seat, player).
Exit vehicle
csharp
public static void exitVehicle()Sends the player's current rigidbody velocity with the exit request. The server broadcasts SendExitVehicle which calls vehicle.removePlayer(seat, point, angle, forceUpdate).
Swap seat
csharp
public static void swapVehicle(byte toSeat)Sends SendSwapVehicleRequest. On acceptance, the server broadcasts SendSwapVehicleSeats which calls vehicle.swapPlayer(fromSeat, toSeat).
Delegate hooks
| Delegate | Signature | Fires |
|---|---|---|
onEnterVehicleRequested | (Player, InteractableVehicle, ref bool) | Before player enters |
onExitVehicleRequested | (Player, InteractableVehicle, ref bool, ref Vector3, ref float) | Before player exits |
onSwapSeatRequested | (Player, InteractableVehicle, ref bool, byte, ref byte) | Before seat swap |
onVehicleLockpicked | (InteractableVehicle, Player, ref bool) | When lockpick attempt occurs |
onVehicleCarjacked | (InteractableVehicle, Player, ref bool, ref Vector3, ref Vector3) | When empty vehicle is shoved |
Lock and skin state
Lock state
The lock state is toggled through ReceiveVehicleLockRequest (rate-limited at 4 Hz). The server validates that the requesting player is the driver and that the vehicle can be locked. The driver's Steam ID and group ID are used as lock credentials. The lock state is broadcast via SendVehicleLockState to all clients.
csharp
public static void ServerSetVehicleLock(InteractableVehicle vehicle, CSteamID ownerID,
CSteamID groupID, bool isLocked)Unlocking via lockpick calls unlockVehicle, which fires onVehicleLockpicked and — if allowed — clears the lock state via ServerSetVehicleLock with CSteamID.Nil.
Skin state
The skin system supports economy cosmetic items. ReceiveVehicleSkinRequest (rate-limited at 2 Hz) reads the player's equipped skin items from the economy service and broadcasts the skin/mythic ID pair via SendVehicleSkin.
State replication protocol
The most performance-sensitive system in VehicleManager is the batched state replication sent via SendVehicleStates.
The sequenced batch
csharp
private static uint seq;
private static readonly ClientStaticMethod SendVehicleStates = ClientStaticMethod.Get(ReceiveVehicleStates);The server periodically gathers all vehicle states into a single packet with:
- A monotonically increasing sequence number (
seq) - A count of vehicles in this batch
- Per-vehicle:
instanceID, compressed position (ClampedVector3withPOSITION_FRAC_BIT_COUNT), compressed rotation (ReadQuaternionwithROTATION_BIT_COUNT), speed, forward velocity, steering input, and velocity input
The client rejects outdated sequence numbers (newSeq <= seq) to prevent stale state from overwriting newer data.
High-quality detail flag
Each vehicle in the batch carries an includesHighQualityDetails bit. When set, the packet additionally includes per-wheel suspension state (compressed to 4 bits) and ground material NetId. When not set, wheel states default to full extension.
For vehicles that use UsesEngineRpmAndGears, the HQ detail includes the current gear index and normalized engine RPM.
Wheel suspension replication
The asset's replicatedWheelIndices array determines which wheels get their suspension state serialized. At initial spawn, all replicated wheel states are included. During state updates, only the compressed suspension value and ground material are sent (when HQ detail is enabled).
Respawn system
The respawn timer is managed through a round-robin index:
csharp
private static ushort respawnVehicleIndex;
private static float lastTick;The tick method iterates one vehicle per tick (checking respawnVehicleIndex), advancing the index each frame. If the current vehicle is dead and its respawn timer has elapsed, the vehicle is destroyed and a new one is spawned at its original position using the original spawn table.
The respawn delay and the decision to respawn are governed by Provider.modeConfigData.Vehicles.
Destroying vehicles
Single vehicle destruction
csharp
public static void askVehicleDestroy(InteractableVehicle vehicle)Server-side only. Forces all players out of the vehicle, broadcasts SendDestroySingleVehicle, and removes the vehicle from the list.
Bulk destruction
csharp
public static void askVehicleDestroyAll()Iterates all vehicles from last to first, force-removes all players, then broadcasts SendDestroyAllVehicles.
DestroyVehicleCommon
Both destruction paths converge on DestroyVehicleCommon:
- Force-exit any player whose
movement.getVehicle()still references the destroyed vehicle (edge case where enter/exit and destroy race on the same frame). - Uproot any barricades planted on the vehicle and on any train cars.
- Set
vehicle.IsPendingDestroy = true. - Fire
OnPreDestroyVehicleevent. - Release the
NetIdand transform registration. - Call
EffectManager.ClearAttachmentsfor any effects parented to the vehicle. - Destroy the
GameObject.
Decay system
Vehicles have an optional decay system controlled by SAVEDATA_VERSION_ADDED_DECAY = 13. The serversideData stores the lastActive timestamp. When a vehicle has not been interacted with (no player entry, no damage) for a configurable duration, it decays:
- Health is reduced periodically by a decay amount.
- When health reaches zero, the vehicle enters the exploded state.
- Decay only applies to naturally-spawned vehicles (
isNaturalSpawnedfromSAVEDATA_VERSION_ADDED_NATURAL_SPAWNED = 17). - Configuration keys from
Provider.modeConfigData.Vehiclescontrol decay rate, delay, and whether decay is enabled.
getVehiclesInRadius
csharp
public static void getVehiclesInRadius(Vector3 center, float sqrRadius,
List<InteractableVehicle> result)Iterates all vehicles, skipping dead vehicles, and checks squared distance. Because the check iterates the entire vehicles list, performance degrades linearly with total vehicle count. For large maps with many vehicles, consider calling this infrequently or caching results.
Natural spawn tracking
SAVEDATA_VERSION_ADDED_NATURAL_SPAWNED = 17 added the isNaturalSpawned flag. This flag is set on vehicles that were spawned by the natural respawn system (as opposed to admin-spawned or plugin-spawned vehicles). Only naturally-spawned vehicles are eligible for decay and respawn timer management.
Train vehicle handling
Vehicles with EEngine.TRAIN use a special position packing scheme:
csharp
if (vehicle.asset.engine == EEngine.TRAIN)
{
position = InteractableVehicle.PackRoadPosition(vehicle.roadPosition);
}
else
{
position = vehicle.transform.position;
}Train position is packed into a Vector3 where the X coordinate stores the road path position. Train cars are linked via the trainCars array, and barricade uprooting processes each car individually during vehicle destruction.
Engine RPM and gear replication
Vehicles with UsesEngineRpmAndGears (sports cars, performance vehicles) replicate:
- Current gear: Packed as a signed integer (
-1 = reverse, 0 = neutral, 1-N = forward gears). The gear is clamped to the asset'sforwardGearRatiosarray length. - Engine RPM: Normalized to
[0, 1]via 4-bit unsigned normalized float. Deserialized asMathf.Lerp(EngineIdleRpm, EngineMaxRpm, normalizedRpm).
When HQ detail is not included in the state update, the vehicle defaults to gear 1 and idle RPM.
Edge cases in passenger management
Concurrent enter/exit races
DestroyVehicleCommon handles the case where a player has requested to enter or exit a vehicle on the same frame the server is destroying it:
csharp
foreach (SteamPlayer client in Provider.clients)
{
Player player = client.player;
if (player.movement.getVehicle() == vehicle)
{
Debug.Assert(player.movement.hasPendingVehicleChange,
"Player should already be exiting vehicle");
player.movement.ApplyPendingVehicleChange();
Debug.Assert(player.movement.getVehicle() == null,
"Player should no longer be in vehicle");
}
}This ensures no player component remains parented to a destroyed vehicle's transform.
Train vehicle barricade management
Trains consist of multiple linked cars. Each car can have barricades planted on it. When a train is destroyed, the manager must uproot barricades from all cars:
csharp
if (vehicle.trainCars != null)
{
for (int carIndex = 1; carIndex < vehicle.trainCars.Length; ++carIndex)
{
BarricadeManager.uprootPlant(vehicle.trainCars[carIndex].root);
}
}Car index 0 is the primary locomotive (vehicle.transform). Cars at index 1+ are separate transforms stored in the trainCars array. Each car independently tracks its barricade region.
Vehicle search by type
Beyond the instance-ID-based lookup, vehicles can be searched by type using getVehiclesInRadius and filtering by asset. For targeted queries:
csharp
public static InteractableVehicle findVehicleByNetInstanceID(uint instanceID)This is the primary lookup method used by all state update RPCs. Because it scans the entire vehicles list, it is O(n). For state updates that process 50+ vehicles per packet, this linear scan is acceptable because the list is typically small (hundreds, not thousands).
Plugin integration patterns
Damage interception
To create invulnerable vehicles for a specific game mode:
csharp
VehicleManager.onDamageVehicleRequested += (instigator, vehicle, ref damage,
ref canRepair, ref allow, origin) =>
{
if (vehicle.asset.GUID == myInvulnerableVehicleGuid)
allow = false;
};Custom respawn logic
To control where and when vehicles respawn:
csharp
// Disable natural respawn by setting all timers to max
// Then manually call SpawnVehicleV3 when needed
VehicleManager.SpawnVehicleV3(asset, 0, 0, 0f, point, angle,
false, false, false, false,
asset.fuel, asset.health, 10000,
CSteamID.Nil, CSteamID.Nil, false,
null, byte.MaxValue, paintColor);Fuel siphon interception
csharp
VehicleManager.onSiphonVehicleRequested += (vehicle, player,
ref allow, ref desiredAmount) =>
{
// Allow only half the requested amount
desiredAmount = (ushort)(desiredAmount / 2);
};Vehicle writing and reading
WriteVehicle — Serialization
The WriteVehicle method serializes the full state of a vehicle for initial replication:
- Asset GUID
- Skin ID and mythic ID
- Position (or packed road position for TRAIN engine type)
- Rotation (compressed quaternion)
- Boolean flags: sirens, blimp floating, headlights, taillights
- Fuel, exploded flag, health, battery charge
- Lock owner, lock group, locked flag
- Passenger list (Steam IDs for each seat;
CSteamID.Nilfor empty seats) - Instance ID
- Tire alive mask
- NetId
- Paint color
- Replicated wheel suspension states
ReceiveSingleVehicle — Client deserialization
The client deserializes the vehicle from the incoming packet, calls manager.addVehicle to instantiate it, and then reads the replicated wheel suspension states to teleport the wheels into their correct positions.
Plugin integration summary
| Hook | When to use |
|---|---|
onDamageVehicleRequested | Intercept or modify vehicle damage |
onRepairVehicleRequested | Intercept or modify vehicle repair |
onDamageTireRequested | Intercept or cancel tire damage |
onEnterVehicleRequested | Block or allow vehicle entry |
onExitVehicleRequested | Block or modify vehicle exit |
onSwapSeatRequested | Redirect seat changes |
onVehicleLockpicked | Intercept lockpick attempts |
onVehicleCarjacked | Modify carjack force/torque |
onSiphonVehicleRequested | Intercept fuel siphoning |
OnToggleVehicleLockRequested | Intercept lock toggle |
OnPreDestroyVehicle | Clean up before vehicle destruction |
Serialization version history
| Constant | Version | Change |
|---|---|---|
SAVEDATA_VERSION_ADDED_DECAY | 13 | Added decay system |
SAVEDATA_VERSION_REPLACED_ID_WITH_GUID | 14 | Replaced legacy IDs with GUIDs |
SAVEDATA_VERSION_BATTERY_GUID | 15 | Battery GUID support |
SAVEDATA_VERSION_ADDED_PAINT_COLOR | 16 | Paint color field |
SAVEDATA_VERSION_ADDED_NATURAL_SPAWNED | 17 | Natural spawn tracking |
The addVehicle instantiation method
The internal addVehicle method is the factory for all InteractableVehicle instances. It is called from both SpawnVehicleV3 (server-originated spawn) and ReceiveSingleVehicle (client-side deserialization). The method:
- Resolves the
VehicleAssetfrom the provided GUID. - Selects the prefab based on build context:
- Dedicated server:
Vehicle_Dedicated(no renderers, no audio). - Listen server:
Vehicle_Server(minimal visuals). - Client: Full prefab with all components.
- Dedicated server:
- Instantiates the prefab at the configured position and rotation.
- Assigns the vehicle's identity:
instanceIDfromallocateInstanceID().skinIDandmythicIDfor cosmetic item display.roadPositionfor train alignment.
- Applies vehicle state:
- Fuel, health, battery charge, exploded flag.
- Lock status:
lockedOwner,lockedGroup,isLocked. tireAliveMask: Each bit indicates whether a tire is present.- Paint color: 32-bit RGBA stored as
Color32. - Turret data:
byte[][]where each entry is the turret's ammo/state array.
- Registers the vehicle:
- Assigns the
NetIdblock (NETIDS_PER_VEHICLE IDs per vehicle). - Registers the transform at
NetId + 1for collision network sync. - Adds the vehicle to the
_vehicleslist.
- Assigns the
- Initializes decay state:
- Sets
lastActivetimestamp. - Clears decay flags for newly spawned vehicles.
- Sets
Network ID allocation
Each vehicle consumes a contiguous block of NetId values:
csharp
NetId netId = NetIdRegistry.ClaimBlock(NETIDS_PER_VEHICLE);The allocation ensures:
netId + 0— Primary NetId for theInteractableVehiclecomponent.netId + 1— Transform NetId registered atNetIdRegistry.AssignTransform.
During destruction, the transform NetId is released before the primary NetId:
csharp
NetIdRegistry.ReleaseTransform(vehicle.GetNetId() + 1, vehicle.transform);
vehicle.ReleaseNetId();Server-side initial state to clients
SendInitialGlobalState handles the batched transmission of all vehicles to a newly connected client. The batching at 50 vehicles per packet prevents network congestion:
csharp
const int MAX_TELLVEHICLES_PER_PACKET = 50;For a map with 200 vehicles, this means 4 packets instead of 1 large one. Each packet is sent reliably (ENetReliability.Reliable). After all vehicle packets are sent, BarricadeManager.SendVehicleRegions transmits the barricades planted on vehicles.
Physics profile hash sync
When entering a vehicle, the client sends the vehicle's PhysicsProfileAsset hash to the server:
csharp
VehiclePhysicsProfileAsset physicsProfile = vehicle.asset.physicsProfileRef.Find();
byte[] physicsProfileHash = physicsProfile != null ? physicsProfile.hash : new byte[0];
SendEnterVehicleRequest.Invoke(ENetReliability.Unreliable, vehicle.instanceID,
vehicle.asset.hash, physicsProfileHash, (byte)vehicle.asset.engine);This allows the server to validate that the client has the correct physics profile for the vehicle. Mismatched profiles can cause desync in vehicle handling.
Headlight battery drain prevention
The client-side sendVehicleHeadlights method includes a defensive check:
csharp
if (!vehicle.asset.hasHeadlights)
{
if (wantsHeadlightsOn) return;
}This prevents clients from accidentally draining the battery by toggling headlights on vehicles that don't visually support them. The server does not know whether a vehicle has headlights (the asset may differ between client and server due to mod mismatch), so this is a client-side safeguard.
Key type references
InteractableVehicle— The runtime vehicle component on the spawned prefabVehicleAsset— The asset definition (2809 lines) with speed, fuel, health, turret, wheel, and engine configurationVehicleRedirectorAsset— Redirector asset that maps a legacy ID to a target vehicle with optional paint color overrideVehiclePhysicsProfileAsset— Physics tuning asset with mass, drag, friction, suspension, and torque profilesPassenger— Seat occupant data: player reference and seat indexWheel— Per-wheel data: suspension state, ground material, steering mode
addVehicle — Internal instantiation
The addVehicle method (called by both SpawnVehicleV3 and ReceiveSingleVehicle) is the single point of InteractableVehicle creation. It:
- Resolves the
VehicleAssetfrom theGUID. - Loads the appropriate prefab (dedicated server prefab vs client prefab).
- Instantiates the prefab at the given position and rotation.
- Applies the asset's configuration: sets
instanceID,skinID,mythicID,roadPosition, fuel, health, battery charge,isExplodedflag, lock state (owner + group + locked flag),tireAliveMask, paint color, andNetId. - Configures turret ammunition from the turret data arrays.
- Applies decay data and last active timestamp.
- Assigns the vehicle to the global
vehicleslist.
The dedicated server uses a stripped-down prefab (Vehicle_Dedicated) that lacks renderers, audio sources, and other client-only components. The client prefab includes the full visual and audio model.
Server-side enter/exit handling
Enter request processing
ReceiveEnterVehicleRequest validates:
- The requesting player is connected and alive.
- The vehicle exists, is not dead, and is not full.
- The player is within interaction range.
- The vehicle is not locked against the player (or the player has ownership).
If validation passes, the server broadcasts SendEnterVehicle with the vehicle instance ID, seat index, and player Steam ID.
Exit request processing
ReceiveExitVehicleRequest validates:
- The requesting player is in the vehicle.
- The exit point is safe (the player will not be teleported inside terrain or a barricade).
On accept, the server broadcasts SendExitVehicle with the exit position and yaw.
Seat swap
ReceiveSwapSeatRequest validates:
- The target seat exists on the vehicle.
- The target seat is empty (or the player can swap through occupied seats).
- The
onSwapSeatRequesteddelegate does not redirect.
Force remove all players
forceRemoveAllPlayers is called during vehicle destruction. It iterates all passengers, calls removePlayer on each seat, and asserts isEmpty afterward. Developers must be aware of the edge case where a player's movement.getVehicle() still references the vehicle mid-change — DestroyVehicleCommon handles this by applying any pending vehicle changes before uprooting barricades.
The vehicle tick and respawn loop
The tick method is called every frame from the game's update loop:
csharp
private static float lastTick;On each tick, the manager processes the vehicle at respawnVehicleIndex:
- If the vehicle is not dead, skip.
- If the vehicle was recently destroyed (exploded), check if the explosion timer has elapsed.
- If the vehicle should respawn, destroy the existing dead vehicle and spawn a new one using the original spawn table data.
- Advance
respawnVehicleIndexwrapping around to0when it reachesvehicles.Count.
The respawn system relies on shouldRespawnReloadedVehicles flag. When a vehicle asset is replaced (via hot reload), this flag triggers respawn of all vehicles of that type.
Battery system
The battery is a separate entity from the vehicle's fuel system. The batteryCharge field (ushort) represents the battery's current charge level, and ReceiveVehicleBatteryCharge synchronizes it independently from fuel.
The battery GUID system (SAVEDATA_VERSION_BATTERY_GUID = 15) allows vehicles to reference a specific battery item asset for the battery that spawns with the vehicle.
Headlight, siren, blimp, and horn synchronization
Headlights
ReceiveToggleVehicleHeadlights (rate-limited at 10 Hz) validates:
- The requesting player is alive and in the vehicle.
- The vehicle supports headlights (
asset.hasHeadlights). - The player is the driver.
- The
wantsHeadlightsOnstate differs from the current state. - The vehicle has enough battery charge (checked via
canTurnOnLights).
On acceptance, SendVehicleHeadlights broadcasts the new state.
Sirens and blimp
Sirens (SendVehicleSirens) and blimp float state (SendVehicleBlimp) are synchronized via their respective RPCs. The client determines the bonus mode (0 = siren, 1 = hook, 2 = blimp) in sendVehicleBonus because the server does not know which features the vehicle asset supports.
Horn
SendVehicleHorn is a simple event notification — it has no on/off toggle, just a "play the horn" signal.
Physics profile and carjack mechanics
The carjackVehicle method applies force to an empty vehicle's rigidbody:
csharp
public static void carjackVehicle(InteractableVehicle vehicle,
Player instigatingPlayer, Vector3 force, Vector3 torque)The force is scaled by:
physicsProfile.carjackForceMultiplierfrom theVehiclePhysicsProfileAsset(if present).vehicle.asset.carjackForceMultiplier.
The onVehicleCarjacked delegate allows plugins to modify force and torque or cancel the action entirely.
Wheel suspension and ground material
During state updates with HQ detail enabled, each replicated wheel receives:
replicatedSuspensionState: a 4-bit normalized float where 0 = fully compressed and 1 = fully extended.replicatedGroundMaterial: aPhysicsMaterialNetIdthat identifies the surface type (concrete, grass, gravel, metal, etc.).
These values drive the vehicle's visual suspension animation and tire friction during rendering.
Initial replication to new clients
SendInitialGlobalState sends vehicles to newly connecting clients in batches of 50:
csharp
const int MAX_TELLVEHICLES_PER_PACKET = 50;
int requiredPacketCount = ((totalVehicleCount - 1) / MAX_TELLVEHICLES_PER_PACKET) + 1;This prevents a single massive packet from overwhelming the new client. After all vehicle batches are sent, BarricadeManager.SendVehicleRegions is called to send the barricades planted on vehicles.
