Configuration

17 min readUpdated 1 weeks ago

Overview

  • shared/config.lua - everything about how a build works: chassis mode, part list, animations, placement, collaboration, limits, paid layer
  • shared/data/vehicles.lua - the catalogue of craftable vehicles
  • server/config_server.lua - permission tiers and Discord logging

General

shared/config.lua
1Config.Debug = false
2Config.Language = 'en'
3Config.Theme = 'default'
  • Debug - prints diagnostic output to the server console and F8
  • Language - language file loaded from locales/
  • Theme - NUI theme: default, wasteland, cyberpunk, noir, fantasy

Chassis

shared/config.lua
1Config.ChassisMode = 'metadata'
2Config.Items = {
3    chassis = 'vc_chassis',
4}
5Config.ChassisExportResources = {
6    'ml_crafting',
7}
  • ChassisMode - 'metadata' uses one generic chassis item carrying the target vehicle in its metadata; 'items' uses one vc_chassis_<model> item per vehicle and works on any inventory
  • Items.chassis - the generic chassis item name, used by metadata mode only
  • ChassisExportResources - resources allowed to call the give exports
Automatic fallback

On an inventory without metadata support, metadata mode falls back to 'items' and says so in the console. The per-vehicle chassis items have to exist in the inventory for that fallback to work.

Parts

shared/config.lua
1Config.Preset = 'advanced'
2
3Config.Parts = {
4    modelLock = true,
5    quality = false,
6}
  • Preset - how many parts a build takes: 'simple' (2), 'intermediate' (5), 'advanced' (9)
  • Parts.modelLock - a part belongs to one vehicle. An Elegy door only fits an Elegy, and a door pulled off a Sultan is useless on it. Off means any door fits any vehicle
  • Parts.quality - a part carries a condition from 0 to 100, and the weighted average of the installed parts decides the health of the finished vehicle. Dismantling a part to reuse it wears it down

Both switches need an inventory with metadata support. On an inventory without one they turn themselves off and the script warns on start.

Before turning quality on

Every source of parts on the server has to stamp a condition on the item. Parts that arrive without one count as Config.Quality.defaultValue. At a value below 100, a loot table or a recipe that forgot the stamp silently produces half-broken vehicles and nothing points at the cause.

shared/config.lua
1Config.Presets = {
2    simple = {
3        partTypes = { 'engine', 'wheel' },
4        installTimeMul = 0.6,
5        dismantle = { qualityLoss = 0 },
6    },
7    intermediate = {
8        partTypes = { 'engine', 'transmission', 'wheel', 'door', 'bonnet' },
9        installTimeMul = 1.0,
10        dismantle = { qualityLoss = 10 },
11    },
12    advanced = {
13        partTypes = { 'engine', 'transmission', 'brakes', 'wheel', 'door', 'seat', 'bonnet', 'boot', 'exhaust' },
14        installTimeMul = 1.4,
15        dismantle = { qualityLoss = 20 },
16    },
17}
  • partTypes - which part types the build asks for
  • installTimeMul - multiplies the install time of every part
  • dismantle.qualityLoss - condition lost when a part is pulled back off, only used while Config.Parts.quality is on

Part types

Each entry in Config.PartTypes defines one kind of part.

shared/config.lua
1engine = {
2    item = 'vc_engine', bone = 'engine', count = 1,
3    installTime = 15000, weight = 3,
4    prop = { model = 'prop_car_engine_01', pos = vec3(0.025, 0.0, 0.15), rot = vec3(90.0, 0.0, 180.0) },
5    anim = { dict = 'creatures@rottweiler@tricks@', clip = 'petting_franklin' },
6    hoist = true,
7},
8wheel = {
9    item = 'vc_wheel', bone = { 'wheel_lf', 'wheel_rf', 'wheel_lr', 'wheel_rr' },
10    count = 'auto', installTime = 6000, weight = 1,
11    prop = { model = 'prop_wheel_01' },
12    requires = { 'brakes' },
13},
  • item - inventory item consumed to install it, overridable per vehicle through partItems
  • bone - the vehicle bone the part mounts on. A list means one slot per bone, each walked to and mounted separately
  • offset - marker position relative to the shell, for parts with no bone
  • count - how many slots the build asks for. A number is fixed and 0 removes the part entirely; 'auto' reads it from the model, so a boat gets no wheels and a bike gets two. This is the main knob for how heavy a build feels
  • installTime - base duration in milliseconds, scaled by the preset
  • weight - how much this part counts in the condition average of the finished vehicle
  • prop - the model held during the carry, with optional pos, rot and bone to tune the attach
  • anim, fx - per-type override of the defaults in Config.Install
  • requires - other part types that must be installed first, enforced on both ends. The chain also runs backwards: the hood cannot come off while the engine is in
  • hoist - lower the part in on an engine hoist instead of carrying it
The count cannot be shrunk from the client

The server recomputes every count from the config. For 'auto' types it clamps the detected number to a per-class minimum, so a car can never be crafted with fewer than four wheels, two doors and two seats.

Install sequence

shared/config.lua
1Config.Install = {
2    anim = { dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', clip = 'machinic_loop_mechandplayer' },
3    carry = {
4        anim = { dict = 'anim@heists@box_carry@', clip = 'idle' },
5        time = 1200,
6    },
7    fx = { dict = 'core', name = 'ent_amb_sparking_wires', scale = 0.4 },
8    propBone = 56604,
9    propPos = vec3(-0.08, 0.30, 0.37),
10    propRot = vec3(0.0, 0.0, 180.0),
11    broadcastRadius = 30.0,
12    sounds = {
13        enabled = true,
14        mountStart = { name = 'Drill_Pin_Break', set = 'DLC_HEIST_FLEECA_SOUNDSET' },
15        mountDone  = { name = 'PICK_UP', set = 'HUD_FRONTEND_DEFAULT_SOUNDSET' },
16    },
17    walkToPart = true,
18    walkStopDistance = 1.2,
19    walkTimeout = 6000,
20    hoistProp = 'prop_engine_hoist',
21    hoistRaise = 0.9,
22}
  • anim - animation for placement, mounting and salvage. A part type's own anim wins over it
  • carry.anim - the pose held while carrying the part to the vehicle
  • carry.time - carry duration when there is no bone to walk to
  • fx - welding sparks played at the mount point
  • propBone, propPos, propRot - default attach of the held part on the ped
  • broadcastRadius - how far away other players see the carry, mount and hoist. 0 keeps it local
  • sounds - mount feedback, vanilla soundset references. Set enabled = false to mute
  • walkToPart - walk to the bone before mounting, when the bone exists
  • walkStopDistance - how close to the bone the player stops
  • walkTimeout - milliseconds before giving up and mounting on the spot
  • hoistProp - stand shown next to the car for hoisted parts. Set false to skip it. The hoist only plays on models that have an engine bone, so boats and bikes fall back to the carry
  • hoistRaise - height above the bay the hoisted part starts from

Cinematic

shared/config.lua
1Config.Cinematic = {
2    enabled = true,
3    duration = 5500,
4    distance = 6.0,
5    height = 1.6,
6    fov = 50.0,
7    turnSpeed = 0.22,
8}

Camera showcase played for the builder when a project finishes. duration is in milliseconds and the player can skip it. turnSpeed is degrees of orbit per frame.

Quality mapping

shared/config.lua
1Config.Quality = {
2    defaultValue = 100,
3    health = { engineMin = 250.0, engineMax = 1000.0, bodyMin = 350.0, bodyMax = 1000.0 },
4}
  • defaultValue - condition assumed for a part with no stamp
  • health - engine and body health of the finished vehicle, mapped linearly from the condition average

With Config.Parts.quality off, every build finalizes at 100 and vehicles spawn at engineMax and bodyMax.

Blueprints

shared/config.lua
1Config.Blueprint = {
2    consume = true,
3}
  • consume - remove the blueprint item when the project starts. It is handed back if the placement fails

Which vehicles need a blueprint is set per entry in shared/data/vehicles.lua.

Placement

shared/config.lua
1Config.Placement = {
2    maxRayDistance = 25.0,
3    duration = 3000,
4    zoneRestricted = false,
5    zones = {
6        -- { coords = vec3(1730.5, 3310.7, 41.2), radius = 150.0 },
7    },
8}
  • maxRayDistance - how far ahead the placement ghost can be pushed
  • duration - progress bar played when the shell is set down
  • zoneRestricted - restrict building to the zones below
  • zones - list of { coords, radius } workshop areas

Carcass

shared/config.lua
1Config.Carcass = {
2    colors = {
3        { primary = 13, secondary = 13 },
4    },
5    streamDistance = 100.0,
6    workDistance = 20.0,
7}
  • colors - GTA paint index pairs. One is picked when the shell is created, saved with the project and shown identically to every player. Add entries to give each shell a random colour
  • streamDistance - how far away the shell stays spawned
  • workDistance - how far a player can walk from the shell and still mount, dismantle and use the crew panel
The shell is not a networked vehicle

Each client spawns its own copy from the project data. The server never owns an entity for it, so it costs no bandwidth and no other script can act on it. It does take a slot in the local vehicle pool, which is what Config.Build.maxNearby protects.

Interaction and board

shared/config.lua
1Config.Interaction = nil
2Config.InteractionDistance = 2.5
3Config.InteractionKey = 38
4Config.InteractionIcon = 'fas fa-wrench'
5
6Config.Dui = {
7    drawDistance = 22.0,
8    heightOffset = 1.55,
9    sizeNear = 2.2,
10    sizeFar = 0.85,
11    style = 'gauge',
12    styles = {
13        gauge     = { width = 700, height = 760, scale = 0.125 },
14        segmented = { width = 760, height = 320, scale = 0.128 },
15        pill      = { width = 820, height = 210, scale = 0.110 },
16    },
17}
  • Interaction - nil lets ml_bridge decide, 'target' forces the target system, 'textui' forces the key prompt
  • InteractionDistance - range of the build and salvage options
  • InteractionKey - control id used in TextUI mode
  • InteractionIcon - icon of the build options in target mode
  • Dui.drawDistance - how close a player must be for the holographic board to appear. The shell itself streams from much further
  • Dui.heightOffset - metres above the shell the board floats
  • Dui.sizeNear, Dui.sizeFar - clamps on the on-screen size, so the board never covers the screen up close nor becomes unreadable far away
  • Dui.style - 'gauge' is a 270 degree dial, 'segmented' a ten-segment bar, 'pill' a single line
  • Dui.styles - resolution and world scale of each style

Collaboration

shared/config.lua
1Config.Collaboration = {
2    install = 'crew',
3    dismantle = 'crew',
4    partsGoTo = 'remover',
5    claimByFinisher = false,
6}
  • install - who may mount parts: 'owner', 'crew' or 'public'
  • dismantle - who may pull parts off. Accepts the same three values plus 'nobody', which welds the project shut for everyone including the owner
  • partsGoTo - 'remover' gives a dismantled part to whoever pulled it; 'owner' returns it to the project owner, or drops it at the shell if they are offline. Set it to 'owner' and griefers can undo work but gain nothing
  • claimByFinisher - whoever presses Finish project owns the vehicle. Off keeps it with the original owner

A safe server runs install = 'crew', dismantle = 'nobody'. A hardcore one runs both 'public' with claimByFinisher = true.

Plate

shared/config.lua
1Config.Plate = {
2    format = '11AAA111',
3}
  • format - 1 is a digit, A a letter, . alphanumeric, anything else is kept literal. Maximum eight characters

The plate is reserved when the shell is placed and kept through to the finished vehicle. It is checked against other projects and against owned vehicles, and re-rolled until free.

Build limits

shared/config.lua
1Config.Build = {
2    maxPerPlayer = 2,
3    staleDays = 14,
4    autoFinalize = false,
5    maxNearby = 6,
6    crowdRadius = 60.0,
7}
  • maxPerPlayer - concurrent projects one player can have open
  • staleDays - delete projects untouched for this long. 0 never deletes
  • autoFinalize - finish the moment the last part goes in. Off leaves the Finish project button to the player
  • maxNearby - refuse a new shell when this many already sit within crowdRadius. 0 removes the cap
  • crowdRadius - radius the density cap counts in

Output

shared/config.lua
1Config.Output = {
2    mode = 'owned',
3    startEngineOff = true,
4}
  • mode - 'owned' registers the finished vehicle through OpenServer.OnFinalized; 'temporary' spawns it without ownership or persistence
  • startEngineOff - the finished vehicle starts with the engine off

Integrations

shared/config.lua
1Config.Integrations = {
2    vehicleKeys = true,
3    fuel = true,
4    fuelStart = 0.0,
5    mechanic = true,
6}
  • vehicleKeys - hand the keys to the owner when the vehicle is finished
  • fuel - set the starting fuel level
  • fuelStart - that level. 0.0 means the first drive is to a pump
  • mechanic - seed the engine condition in ml_mechanic from the part condition average

Each one is skipped when the matching resource is not running.

Tools

shared/config.lua
1Config.Tools = {
2    enabled = false,
3    item = 'toolkit',
4    label = 'Toolkit',
5    breakChance = 0,
6}
  • enabled - require a tool in the inventory to mount or dismantle
  • item - the tool item name
  • label - name used in the messages
  • breakChance - percentage chance the tool is consumed per use

Skill gate

shared/config.lua
1Config.SkillGate = {
2    enabled = false,
3    category = 'mechanic',
4    tiers = { [2] = 5, [3] = 15 },
5}
  • enabled - require a minimum skill level for higher tiers
  • category - the skill category read through OpenServer.GetSkillLevel
  • tiers - vehicle tier mapped to the minimum level

Job gate

shared/config.lua
1Config.JobGate = {
2    enabled = false,
3    actions = {
4        -- build   = { 'mechanic' },
5        -- work    = { 'mechanic' },
6        -- salvage = { 'mechanic' },
7    },
8}
  • enabled - restrict actions to jobs
  • actions.build - starting a project
  • actions.work - mounting and dismantling
  • actions.salvage - stripping a wreck

Each key takes a list of job names. A key left out is not restricted.

Salvage

shared/config.lua
1Config.Salvage = {
2    enabled = false,
3    models = {},
4    duration = 9000,
5    skillCheck = false,
6    toolRequired = true,
7    deleteVehicle = true,
8    cooldown = 5000,
9    resalvageBlock = 900000,
10    yield = {
11        { item = 'vc_wheel',   min = 1, max = 3, chance = 0.70 },
12        { item = 'vc_door',    min = 1, max = 2, chance = 0.50 },
13        { item = 'vc_seat',    min = 1, max = 2, chance = 0.50 },
14        { item = 'vc_exhaust', min = 1, max = 1, chance = 0.40 },
15        { item = 'vc_bonnet',  min = 1, max = 1, chance = 0.35 },
16        { item = 'vc_engine',  min = 1, max = 1, chance = 0.20 },
17    },
18}
  • enabled - allow stripping vehicles for parts
  • models - the spawn names that can be stripped. The option appears on every vehicle in the world with one of these models
  • duration - milliseconds the strip takes
  • skillCheck - false, or a list of ox_lib difficulties such as { 'easy', 'easy', 'medium' }
  • toolRequired - also obey Config.Tools while salvaging
  • deleteVehicle - remove the vehicle once stripped
  • cooldown - milliseconds between salvages, per player
  • resalvageBlock - milliseconds a stripped vehicle stays empty
  • yield - rolled loot, chance from 0.0 to 1.0
Choose the models carefully

The list is matched by model, not by condition, so every vehicle of that model becomes strippable wherever it stands. A model players can own or buy turns their parked vehicle into a target. Vehicles registered to a player are refused by the server, but the safe list is still junk models only. Common traffic models also make parts free: GTA respawns traffic constantly, and only cooldown stands between a player and an endless supply.

Admin

shared/config.lua
1Config.Admin = {
2    command = 'vehiclecraft',
3}
4
5Config.Commands = {
6    giveChassis  = 'vc_givechassis',
7    giveParts    = 'vc_giveparts',
8    grantUnlock  = 'vc_grantunlock',
9    revokeUnlock = 'vc_revokeunlock',
10    giveCredit   = 'vc_givecredit',
11}
  • Admin.command - opens the admin panel
  • Commands - console and chat command names. Rename them freely, the rank each one needs is set by the permission tiers

Permissions

server/config_server.lua
1Config.Permissions = {
2    Admin = {
3        useDefaultAdmins = true,
4        ace = { 'ml_vehiclecraft.admin', 'group.admin', 'group.superadmin' },
5    },
6    Dangerous = {
7        useDefaultAdmins = true,
8        ace = { 'ml_vehiclecraft.admin', 'group.superadmin' },
9    },
10    GiveItems = {
11        useDefaultAdmins = true,
12        ace = { 'ml_vehiclecraft.admin', 'group.superadmin' },
13    },
14    Premium = {
15        useDefaultAdmins = false,
16        ace = { 'ml_vehiclecraft.premiumadmin' },
17    },
18}
  • Admin - open the panel, look at projects, teleport to them
  • Dangerous - delete a project, force it complete, force a single slot on or off, swap a project to another vehicle
  • GiveItems - spawn chassis and parts from the panel
  • Premium - grant and revoke unlocks and paid uses

A tier passes if the player is a framework admin, when useDefaultAdmins is true, or holds any of the listed aces. Buttons a tier cannot use are hidden from the panel, and the server refuses the action regardless.

Premium is ace only

Premium ships with useDefaultAdmins = false because it hands out things bought with real money. A normal framework admin cannot reach it. Removing the whole Config.Permissions block leaves Admin open to any framework admin and locks Premium for everyone.

Logging

server/config_server.lua
1Config.Logs = {
2    enabled = false,
3    webhook = '',
4    provider = 'discord',
5    webhooks = {
6        build    = '',
7        part     = '',
8        finalize = '',
9        salvage  = '',
10        premium  = '',
11        admin    = '',
12    },
13}
  • enabled - master switch
  • webhook - fallback used when a category below is empty
  • provider - 'discord', 'fivemanage', 'ox_lib', 'both' or 'all'
  • webhooks.build - project started or abandoned
  • webhooks.part - part mounted or dismantled
  • webhooks.finalize - vehicle completed
  • webhooks.salvage - wreck stripped
  • webhooks.premium - unlocks, uses, paid swaps
  • webhooks.admin - admin panel actions

Every entry carries the vehicle, project number, plate, person, position and the detail of the event. Set any webhook to false to disable that log entirely.

Vehicle catalogue

shared/data/vehicles.lua
1['emperor'] = {
2    label = 'Rusty Sedan',
3    description = 'A tired four-door. Runs, mostly.',
4    class = 'automobile',
5    tier = 1,
6    chassis = 'vc_chassis_emperor',
7    blueprint = false,
8},
9['rebel'] = {
10    label = 'Off-road 4x4',
11    description = 'Farm truck with a taste for mud.',
12    class = 'automobile',
13    tier = 2,
14    chassis = 'vc_chassis_rebel',
15    blueprint = 'vc_bp_rebel',
16    swapCost = 2,
17},

The key of each entry is the GTA spawn name. Adding a vehicle means pasting its spawn name and giving it a label.

  • label - the name players read. The key is never shown to them
  • description - one line of flavour shown in the panel
  • class - 'automobile', 'bike', 'boat', 'heli', 'plane' or 'trailer'
  • tier - 1 to 3, used for sorting and the skill gate
  • chassis - the per-vehicle chassis item, used by Config.ChassisMode = 'items'
  • blueprint - false, or a blueprint item also required to start the project
  • model - only needed to give one car two different recipes. Give the entries two different keys and point both at the same spawn name. Left out, the key is the spawn name
  • partItems - per-type item overrides for this vehicle
  • donorOnly - buildable only by players holding that vehicle's unlock, enforced while the paid layer is on
  • swapCost, instantCost - uses spent on this vehicle by the paid actions
  • counts - fixed slot counts, for example counts = { wheel = 4, door = 0 }. Overrides what the model reports, and is the way to allow a doorless build past the per-class minimums
shared/config.lua
1Config.Premium = {
2    enabled = false,
3    redeemCommand = 'vcredeem',
4    actions = {
5        swap          = { enabled = true, cost = 1 },
6        instantFinish = { enabled = true, cost = 1 },
7    },
8    packages = {
9        ['ml_vehiclecraft_uses_3']  = { uses = 3,  eur = 8  },
10        ['ml_vehiclecraft_uses_5']  = { uses = 5,  eur = 12 },
11        ['ml_vehiclecraft_uses_10'] = { uses = 10, eur = 20 },
12        ['ml_vehiclecraft_car_imperator'] = { unlock = 'imperator', chassis = 'imperator', eur = 20 },
13    },
14}
  • enabled - master switch for the whole layer. Off, nothing paid renders or runs
  • redeemCommand - command that opens the player screen with their balance, their unlocks and the redeem box. Set false to hide it
  • actions.swap - convert a project into another vehicle keeping the parts already installed
  • actions.instantFinish - complete a project with no parts
  • actions.*.cost - how many uses one action spends
  • packages - store package id mapped to what it grants. uses adds to the balance, unlock plus chassis grant a permanent vehicle unlock and its chassis

There is one currency: uses. One use pays for any paid action. Uses are a per-player database balance, not items, so nothing is tradeable, dupeable or lootable. The script never takes in-game money.

The donor gate follows the master switch

While Config.Premium.enabled is false, donorOnly is not enforced. The vehicles marked as donor-only in the catalogue are buildable by anyone holding their chassis.

Vehicle Crafting Configuration, FiveM Docs | Micio Mods