Developer

6 min readUpdated 3 days ago

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 boolean
  • GiveParts(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 boolean
  • GivePart(src, partType, vehKey, count?) - gives one part type. partType is a key of Config.PartTypes. count defaults to 1. Returns a boolean
lua
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 label
  • MLAdminOpen() - 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:

lua
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:

lua
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:

server/your_shop.lua
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:

lua
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:

server/your_loot.lua
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)
8end

Weight 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:

lua
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:

lua
1exports.ox_inventory:AddItem(src, 'vc_chassis', 1, { model = 'elegy2' })
Model lock off means no metadata at all

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

open/server.lua
1function OpenServer.CanStartBuild(src, vehKey)
2    return true
3end

Runs before the item and blueprint checks. Return false to block the project.

BeforeFinalize

open/server.lua
1function OpenServer.BeforeFinalize(src, build)
2    return true
3end

Runs when a completed project is about to become a vehicle. Return false to stop it. The project stays intact.

OnFinalized

open/server.lua
1function OpenServer.OnFinalized(src, build, vehicle, plate)
2end

Registers 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

open/server.lua
1function OpenServer.PlateExists(plate)
2    return false
3end

Returns 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

open/server.lua
1function OpenServer.GetSkillLevel(src, category)
2    return 0
3end

Player level in a skill category, used by Config.SkillGate. Wired to ml_skills by default.

CanJobAction

open/server.lua
1function OpenServer.CanJobAction(src, action)
2    return true
3end

Job check for build, work and salvage, used by Config.JobGate.

HasUnlock

open/server.lua
1function OpenServer.HasUnlock(src, vehKey)
2    return false
3end

Whether a player owns the unlock for a donor-only vehicle. The default reads the unlocks table and an optional per-vehicle ace.

ChargeForPremium

open/server.lua
1function OpenServer.ChargeForPremium(src, action, cost)
2    return true
3end

Spends 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

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

open/client.lua
1function OpenClient.BeforeOpenPanel(buildId)
2    return true
3end

Runs before the build panel opens. Return false to block it.

OnBuildFinalized

open/client.lua
1function OpenClient.OnBuildFinalized(vehicle, vehKey)
2end

Runs 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 tier
  • vc_givechassis <playerId> <vehKey> - GiveItems tier
  • vc_giveparts <playerId> <vehKey> - GiveItems tier
  • vc_grantunlock <playerId> <vehKey> - Premium tier
  • vc_revokeunlock <playerId> <vehKey> - Premium tier
  • vc_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 login
  • OnTebexRefund - 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.

Vehicle Crafting Developer, FiveM Docs | Micio Mods