Developer
Overview
Integration happens in two places. open/client.lua and open/server.lua hold the hooks, in plain Lua, for gating and the moment a vehicle is registered to its owner. The exports hand out a chassis or a part already stamped for a given vehicle, which is how a shop, a loot table or a crafting bench feeds the system.
The identifier used everywhere is the vehicle's GTA spawn name, which is also its key in shared/data/vehicles.lua.
Server exports
Callable only by the resources listed in Config.ChassisExportResources.
GiveChassis(src, vehKey)- gives one chassis for that vehicle, stamped. Returns a booleanGiveParts(src, vehKey)- gives the whole set of parts for that vehicle in the right amounts, four wheels, four brakes, as many doors and seats as the model has. Returns a booleanGivePart(src, partType, vehKey, count?)- gives one part type.partTypeis a key ofConfig.PartTypes.countdefaults to 1. Returns a boolean
1exports.ml_vehiclecraft:GiveChassis(src, 'elegy2')
2exports.ml_vehiclecraft:GiveParts(src, 'elegy2')
3exports.ml_vehiclecraft:GivePart(src, 'engine', 'elegy2', 1)Client exports
GetVehicleList()- returns the catalogue as{ { vehKey, label, tier }, ... }, sorted by tier then labelMLAdminOpen()- opens the admin panel, subject to the same permission check as the command
Metadata
With Config.ChassisMode = 'metadata' and Config.Parts.modelLock on, chassis and parts are generic items stamped with the vehicle they belong to. Any system that writes the item itself has to write the same metadata.
Chassis, item vc_chassis:
1metadata = {
2 model = 'elegy2',
3 label = 'Elegy RH8 Chassis',
4}model is required and decides what gets built. label is cosmetic and only changes the name shown in the inventory; the exports fill it from the catalogue.
Part, items vc_engine, vc_transmission, vc_brakes, vc_wheel, vc_door, vc_seat, vc_bonnet, vc_boot, vc_exhaust:
1metadata = {
2 model = 'elegy2',
3 quality = 90,
4}model is required only while Config.Parts.modelLock is on, quality only while Config.Parts.quality is on.
Feeding the parts economy
The script does not sell, spawn or loot parts. Pick whichever of these matches the server.
Any shop that can run a server callback on purchase. Stamp through the export so the metadata is always right:
1RegisterNetEvent('yourshop:bought', function(product)
2 local src = source
3 if product == 'chassis_emperor' then
4 exports.ml_vehiclecraft:GiveChassis(src, 'emperor')
5 elseif product == 'engine_emperor' then
6 exports.ml_vehiclecraft:GivePart(src, 'engine', 'emperor', 1)
7 end
8end)Add your shop resource to Config.ChassisExportResources or the export refuses the call.
If the crafting system can attach metadata to a recipe output, set it directly:
1output = {
2 item = 'vc_engine',
3 count = 1,
4 metadata = { model = 'elegy2' },
5}If it cannot, call the export in the recipe's give step instead. ml_crafting is already allowed in Config.ChassisExportResources and its recipe editor has a vehicle picker that fills the metadata for you.
Drop this helper in your own server file and call it from the loot roll:
1local PART_TYPES = { 'wheel', 'door', 'seat', 'exhaust', 'bonnet', 'boot', 'brakes', 'transmission', 'engine' }
2local VEHICLES = { 'emperor', 'regina', 'blista', 'rebel', 'bison' }
3
4function GiveRandomVehiclePart(src)
5 local partType = PART_TYPES[math.random(#PART_TYPES)]
6 local vehKey = VEHICLES[math.random(#VEHICLES)]
7 return exports.ml_vehiclecraft:GivePart(src, partType, vehKey, 1)
8endWeight the tables to taste. Engines and transmissions are the expensive slots, wheels and panels the common ones.
When the item is written directly, the metadata has to be written with it:
1Bridge.GiveItem(src, 'vc_chassis', 1, { model = 'elegy2', label = 'Elegy RH8 Chassis' })
2Bridge.GiveItem(src, 'vc_engine', 1, { model = 'elegy2', quality = 90 })Or straight through the inventory:
1exports.ox_inventory:AddItem(src, 'vc_chassis', 1, { model = 'elegy2' })With Config.Parts.modelLock = false a part fits any vehicle, so a plain /giveitem vc_engine is enough and none of the above is needed for parts. The chassis still carries its vehicle unless Config.ChassisMode is set to 'items'.
Server handlers
Located in open/server.lua.
CanStartBuild
1function OpenServer.CanStartBuild(src, vehKey)
2 return true
3endRuns before the item and blueprint checks. Return false to block the project.
BeforeFinalize
1function OpenServer.BeforeFinalize(src, build)
2 return true
3endRuns when a completed project is about to become a vehicle. Return false to stop it. The project stays intact.
OnFinalized
1function OpenServer.OnFinalized(src, build, vehicle, plate)
2endRegisters the finished vehicle with the garage system. The shipped default writes a Qbox owned vehicle; QBCore and ESX versions are in the file, commented. Only runs while Config.Output.mode is 'owned'.
PlateExists
1function OpenServer.PlateExists(plate)
2 return false
3endReturns true when a plate already belongs to an owned vehicle, so the generator re-rolls. The default queries player_vehicles on Qbox and QBCore and owned_vehicles on ESX. It is also what stops a player's own vehicle from being salvaged.
GetSkillLevel
1function OpenServer.GetSkillLevel(src, category)
2 return 0
3endPlayer level in a skill category, used by Config.SkillGate. Wired to ml_skills by default.
CanJobAction
1function OpenServer.CanJobAction(src, action)
2 return true
3endJob check for build, work and salvage, used by Config.JobGate.
HasUnlock
1function OpenServer.HasUnlock(src, vehKey)
2 return false
3endWhether a player owns the unlock for a donor-only vehicle. The default reads the unlocks table and an optional per-vehicle ace.
ChargeForPremium
1function OpenServer.ChargeForPremium(src, action, cost)
2 return true
3endSpends the paid uses. Return false to refuse the action. RefundPremium(src, action, cost) is its mirror and runs when an action fails after being charged.
Client handlers
Located in open/client.lua.
Notifications are sent through ml_bridge. Set Config.Notify in ml_bridge/config.lua to pick the system: auto, ox, qb, esx, wasabi, okok, mythic, pnotify, tnotify, brutal, lation, r_notify, fl, zsx.
BeforeOpenPanel
1function OpenClient.BeforeOpenPanel(buildId)
2 return true
3endRuns before the build panel opens. Return false to block it.
OnBuildFinalized
1function OpenClient.OnBuildFinalized(vehicle, vehKey)
2endRuns on the builder's client once the finished vehicle exists, before the camera showcase.
Commands
Every command also runs from the server console, where the permission check is skipped.
/vehiclecraft- opens the admin panel. Admin tiervc_givechassis <playerId> <vehKey>- GiveItems tiervc_giveparts <playerId> <vehKey>- GiveItems tiervc_grantunlock <playerId> <vehKey>- Premium tiervc_revokeunlock <playerId> <vehKey>- Premium tiervc_givecredit <playerId> [amount]- Premium tier, amount defaults to 1 and is capped at 999
/vcredeem is a player command, not an admin one, and only exists while the paid layer is on.
Store integration
server/tebex_integration.lua registers the packages from Config.Premium.packages with ml_tebex and exposes two exports it calls back:
OnTebexPackage- grants uses, or an unlock plus its chassis. Works for offline purchases: the balance and the unlocks live in the database, so a player who bought from the web store gets them on next loginOnTebexRefund- revokes a chargeback. Removes the unlock and claws back unused uses, and reports whether the revoke was clean, partial or impossible
Both are ignored when ml_tebex is not running.
/vcredeem opens the player screen: their balance, their unlocks and a box to enter a purchase id. The box hands the id to ml_tebex, which verifies it against the store and delivers. Without ml_tebex the box answers that redemption is unavailable, and uses are granted by the admin panel instead.