Creating Vehicle Extensions
How to create Lua extensions that run in the Vehicle Engine (VE) context — structure, hooks, and differences from GE extensions.
Vehicle extensions run inside each vehicle's own Lua VM, giving you direct access to physics nodes, beams, electrics, and powertrain data. If you want to add custom behavior to a specific vehicle — new instruments, driver aids, sensor logic, or debug tools — a VE extension is how you do it.
File Location
VE extensions live in lua/vehicle/extensions/:
lua/vehicle/extensions/mymod/myfeature.luaFor mods, this path goes inside your mod's directory structure:
mods/unpacked/my_mod/lua/vehicle/extensions/mymod/myfeature.luaAuto-Loading
Place a VE extension in the auto/ subdirectory to have it load automatically with every vehicle:
lua/vehicle/extensions/auto/mymod/myfeature.luaExtensions outside auto/ must be loaded explicitly — either from JBeam, from another extension, or via console command.
Basic Structure
A VE extension follows the same module pattern as GE extensions:
local M = {}
function M.onInit()
log('I', 'myVEMod', 'Vehicle extension loaded')
end
function M.onUpdate(dt)
-- Called every physics frame
-- dt = delta time in seconds
end
return MThe module table M is returned at the end. Only functions attached to M are accessible from outside.
Available Hooks
VE extensions can implement these hooks (among others):
| Hook | When it fires |
|---|---|
onInit() | Extension is first loaded |
onUpdate(dt) | Every physics frame |
onReset() | Vehicle is reset (R key) |
onBeamBroken(id, energy) | A beam breaks |
onCouplerAttached(nodeId, obj2id, obj2nodeId) | Coupler connects |
onCouplerDetached(nodeId, obj2id, obj2nodeId) | Coupler disconnects |
Use Ctrl+L to reload VE extensions live during development.
How VE Differs from GE
| Vehicle Engine (VE) | Game Engine (GE) | |
|---|---|---|
| Scope | One Lua VM per vehicle | Single global Lua VM |
| Access | Nodes, beams, electrics, powertrain, wheels | World state, traffic, UI, map, extensions |
| File path | lua/vehicle/extensions/ | lua/ge/extensions/ |
| Reload | Ctrl+L | Ctrl+S |
| Use case | Vehicle-specific behavior | Game-wide systems |
VE extensions can directly read vehicle physics data — node positions, beam stress, electrical values, wheel state. GE extensions manage the broader game world.
Accessing Vehicle Data
Inside a VE extension, you have access to vehicle-specific APIs:
function M.onUpdate(dt)
-- Read electrical values (speed, RPM, signals, etc.)
local speed = electrics.values.wheelspeed or 0
local rpm = electrics.values.rpm or 0
-- Access powertrain devices
local engine = powertrain.getDevice("mainEngine")
if engine then
local torque = engine.outputTorque1
end
endCommunicating with GE
VE and GE run in separate Lua VMs. To communicate between them:
VE → GE
-- From inside a VE extension, send a command to GE
obj:queueGameEngineLua("extensions.mymod_mygefeature.onVehicleData(" .. dumps(data) .. ")")GE → VE
-- From a GE extension, send a command to a specific vehicle
local veh = be:getPlayerVehicle(0)
if veh then
veh:queueLuaCommand("extensions.mymod_myvefeature.doSomething()")
endThis string-based communication means you serialize data with dumps() and pass it as Lua code.
Example: Simple Speedometer Logger
local M = {}
local logInterval = 1.0 -- seconds
local timer = 0
function M.onInit()
log('I', 'speedLog', 'Speed logger initialized')
end
function M.onUpdate(dt)
timer = timer + dt
if timer >= logInterval then
timer = timer - logInterval
local speed = electrics.values.wheelspeed or 0
local speedKph = speed * 3.6
log('I', 'speedLog', string.format('Speed: %.1f km/h', speedKph))
end
end
function M.onReset()
timer = 0
end
return MSee Also
- VE Extensions Folder Overview — All 40+ built-in VE extensions
- Creating a GE Extension — GE extension structure and lifecycle
- VE–GE Communication — Communication patterns between VE and GE
- Hook Catalog — All available hooks
- Testing & Debugging — Console, logging, and live reload