Vehicles & driving
Authoritative vehicle paint
Server-controlled RGB paint, replication and change events.
On this page7 sections
Set vehicle paint through the server's canonical state. The selected color is replicated to current viewers, late joiners and clients that stream the vehicle back in.
Permissions and runtime split
Server mutations require world.vehicles:
permissions { "world.vehicles" }
Client reads require vehicles.read:
permissions { "vehicles.read" }
Only the server can change canonical paint. The client API is deliberately read-only.
| Runtime | Function | Purpose |
|---|---|---|
| Server | Open77.vehicles.setPaint(id, paint) |
Apply primary and secondary RGB colors. |
| Server | Open77.vehicles.getPaint(id) |
Read canonical paint. |
| Server | Open77.vehicles.resetPaint(id) |
Restore the vehicle record's authored paint. |
| Server | Open77.vehicles.setLightsHue(id, hue?) |
Tint the car's own lights; nil restores the record's colour. |
| Server | Open77.vehicles.setHeadlightColor(id, color?) |
The same tint from an RGB colour. |
| Server | Open77.vehicles.setAppearance(id, name?) |
Swap the appearance variant. Also needs world.vehicles.appearance. |
| Client | Open77.vehicles.getPaint(id) |
Read paint for one currently streamed vehicle. |
Vehicle operations are cross-resource. Any server resource granted world.vehicles can paint a
canonical vehicle created by another resource; the creator name is not an authorization boundary.
Color formats
Every color accepts one of these equivalent forms:
"#00D8FF"
{ r = 0, g = 216, b = 255 }
{ 0, 216, 255 }
Channels are integers from 0 through 255. Supplying only primary mirrors it to secondary.
Supplying only secondary preserves the current primary color. Custom paint has a separate
applied flag, so { r = 0, g = 0, b = 0 } is valid black paint and never means reset.
Apply, inspect, and reset
local ok = Open77.vehicles.setPaint(vehicleId, {
primary = "#00D8FF",
secondary = { r = 12, g = 24, b = 36 },
})
assert(ok, "unknown canonical vehicle")
local paint = assert(Open77.vehicles.getPaint(vehicleId))
print(paint.applied) -- true
print(paint.primary.r) -- 0
print(paint.secondary.b) -- 36
assert(Open77.vehicles.resetPaint(vehicleId))
assert(Open77.vehicles.getPaint(vehicleId).applied == false)
The compatibility keys primaryColor and secondaryColor are also accepted by setPaint.
For ordinary code, prefer the shorter primary and secondary names.
Initial paint during creation
Paint can be included in the atomic vehicle definition:
local vehicleId = assert(Open77.vehicles.create({
record = "Vehicle.v_standard2_archer_hella_player",
position = { x = -1671.2, y = -710.3, z = 49.9 },
yaw = 90.0,
paint = {
primary = "#101820",
secondary = "#00D8FF",
},
}))
Top-level primaryColor and secondaryColor remain supported. Supplying either form sets the
canonical Open77.vehicles.flags.paintApplied bit automatically.
Events
Server resources receive the canonical vehicle ID and revision as strings:
AddEventHandler("onVehiclePaintChanged", function(id, revision)
local paint = Open77.vehicles.getPaint(tonumber(id))
end)
Client resources receive the same transition after replicated state changes:
AddEventHandler("open77:vehiclePaintChanged", function(id, revision)
local paint = Open77.vehicles.getPaint(id)
end)
The full vehicle snapshot also exposes paintApplied, primaryColor, secondaryColor, and the
nested paint = { applied, primary, secondary, lightsHue } view.
The third channel: the lights hue
The engine struct behind all of this, GenericTemplatePersistentData, carries three things,
not two: primary RGB, secondary RGB and a lights hue. The hue tints the vehicle's own lights,
it is a single float in 0..1 rather than a colour, and it is written to the engine in the SAME
event as the two body colours.
Two consequences follow from that shared event, and both are visible in the API:
- the hue lives inside
paintin a property table, andgetPaint/getPropertiesreport it there; - it is not governed by
paintApplied. A car can wear a tinted headlight over its stock body paint, andresetPaintclears both because it resets the whole template.
nil means the record's own colour rather than zero, because zero is a real hue -- red -- and the
engine struct carries its own lightsColorDefined boolean for exactly that reason.
Open77.vehicles.setLightsHue(vehicleId, 0.55)
Open77.vehicles.setHeadlightColor(vehicleId, "#00D8FF") -- converted to a hue; only the hue survives
Open77.vehicles.clearLightsHue(vehicleId)
This is the closest build 2.31 has to SetVehicleNeonLights or SetVehicleHeadlightsColour, and
it is not the same thing. There is no underglow and no xenon palette in this game. See
Network vehicles for the full list of FiveM vehicle properties with no counterpart
here, and for the getProperties / setProperties round trip that carries all of this at once.
Native presentation and limitations
Open77 applies paint through the vehicle's native visual-customization component and retries after attachment when that component is not ready on the first frame. The stock CrystalCoat/TWINTONE interaction and popup are disabled during an authenticated Open77 session so a player cannot keep a private local override that disagrees with the server.
Arbitrary RGB display still depends on the selected REDengine vehicle record exposing a compatible generic paint component. Unsupported records retain their authored appearance, while the canonical server state stays intact and can still be read by scripts.
See Network vehicles for lifecycle, streaming, seats, damage, and the complete Lua surface.
// For agents & LLMsthis page as Markdownllms.txtllms-full.txt
