Configuration
Overview
shared/config.lua- everything about how a build works: chassis mode, part list, animations, placement, collaboration, limits, paid layershared/data/vehicles.lua- the catalogue of craftable vehiclesserver/config_server.lua- permission tiers and Discord logging
General
1Config.Debug = false
2Config.Language = 'en'
3Config.Theme = 'default'Debug- prints diagnostic output to the server console and F8Language- language file loaded fromlocales/Theme- NUI theme:default,wasteland,cyberpunk,noir,fantasy
Chassis
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 onevc_chassis_<model>item per vehicle and works on any inventoryItems.chassis- the generic chassis item name, used by metadata mode onlyChassisExportResources- resources allowed to call the give exports
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
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 vehicleParts.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.
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.
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 forinstallTimeMul- multiplies the install time of every partdismantle.qualityLoss- condition lost when a part is pulled back off, only used whileConfig.Parts.qualityis on
Part types
Each entry in Config.PartTypes defines one kind of part.
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 throughpartItemsbone- the vehicle bone the part mounts on. A list means one slot per bone, each walked to and mounted separatelyoffset- marker position relative to the shell, for parts with no bonecount- how many slots the build asks for. A number is fixed and0removes 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 feelsinstallTime- base duration in milliseconds, scaled by the presetweight- how much this part counts in the condition average of the finished vehicleprop- the model held during the carry, with optionalpos,rotandboneto tune the attachanim,fx- per-type override of the defaults inConfig.Installrequires- 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 inhoist- lower the part in on an engine hoist instead of carrying it
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
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 ownanimwins over itcarry.anim- the pose held while carrying the part to the vehiclecarry.time- carry duration when there is no bone to walk tofx- welding sparks played at the mount pointpropBone,propPos,propRot- default attach of the held part on the pedbroadcastRadius- how far away other players see the carry, mount and hoist.0keeps it localsounds- mount feedback, vanilla soundset references. Setenabled = falseto mutewalkToPart- walk to the bone before mounting, when the bone existswalkStopDistance- how close to the bone the player stopswalkTimeout- milliseconds before giving up and mounting on the spothoistProp- stand shown next to the car for hoisted parts. Setfalseto skip it. The hoist only plays on models that have an engine bone, so boats and bikes fall back to the carryhoistRaise- height above the bay the hoisted part starts from
Cinematic
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
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 stamphealth- 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
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
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 pushedduration- progress bar played when the shell is set downzoneRestricted- restrict building to the zones belowzones- list of{ coords, radius }workshop areas
Carcass
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 colourstreamDistance- how far away the shell stays spawnedworkDistance- how far a player can walk from the shell and still mount, dismantle and use the crew panel
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
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-nilletsml_bridgedecide,'target'forces the target system,'textui'forces the key promptInteractionDistance- range of the build and salvage optionsInteractionKey- control id used in TextUI modeInteractionIcon- icon of the build options in target modeDui.drawDistance- how close a player must be for the holographic board to appear. The shell itself streams from much furtherDui.heightOffset- metres above the shell the board floatsDui.sizeNear,Dui.sizeFar- clamps on the on-screen size, so the board never covers the screen up close nor becomes unreadable far awayDui.style-'gauge'is a 270 degree dial,'segmented'a ten-segment bar,'pill'a single lineDui.styles- resolution and world scale of each style
Collaboration
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 ownerpartsGoTo-'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 nothingclaimByFinisher- 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
1Config.Plate = {
2 format = '11AAA111',
3}format-1is a digit,Aa 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
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 openstaleDays- delete projects untouched for this long.0never deletesautoFinalize- finish the moment the last part goes in. Off leaves the Finish project button to the playermaxNearby- refuse a new shell when this many already sit withincrowdRadius.0removes the capcrowdRadius- radius the density cap counts in
Output
1Config.Output = {
2 mode = 'owned',
3 startEngineOff = true,
4}mode-'owned'registers the finished vehicle throughOpenServer.OnFinalized;'temporary'spawns it without ownership or persistencestartEngineOff- the finished vehicle starts with the engine off
Integrations
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 finishedfuel- set the starting fuel levelfuelStart- that level.0.0means the first drive is to a pumpmechanic- seed the engine condition inml_mechanicfrom the part condition average
Each one is skipped when the matching resource is not running.
Tools
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 dismantleitem- the tool item namelabel- name used in the messagesbreakChance- percentage chance the tool is consumed per use
Skill gate
1Config.SkillGate = {
2 enabled = false,
3 category = 'mechanic',
4 tiers = { [2] = 5, [3] = 15 },
5}enabled- require a minimum skill level for higher tierscategory- the skill category read throughOpenServer.GetSkillLeveltiers- vehicle tier mapped to the minimum level
Job gate
1Config.JobGate = {
2 enabled = false,
3 actions = {
4 -- build = { 'mechanic' },
5 -- work = { 'mechanic' },
6 -- salvage = { 'mechanic' },
7 },
8}enabled- restrict actions to jobsactions.build- starting a projectactions.work- mounting and dismantlingactions.salvage- stripping a wreck
Each key takes a list of job names. A key left out is not restricted.
Salvage
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 partsmodels- the spawn names that can be stripped. The option appears on every vehicle in the world with one of these modelsduration- milliseconds the strip takesskillCheck-false, or a list ofox_libdifficulties such as{ 'easy', 'easy', 'medium' }toolRequired- also obeyConfig.Toolswhile salvagingdeleteVehicle- remove the vehicle once strippedcooldown- milliseconds between salvages, per playerresalvageBlock- milliseconds a stripped vehicle stays emptyyield- rolled loot,chancefrom 0.0 to 1.0
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
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 panelCommands- console and chat command names. Rename them freely, the rank each one needs is set by the permission tiers
Permissions
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 themDangerous- delete a project, force it complete, force a single slot on or off, swap a project to another vehicleGiveItems- spawn chassis and parts from the panelPremium- 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 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
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 switchwebhook- fallback used when a category below is emptyprovider-'discord','fivemanage','ox_lib','both'or'all'webhooks.build- project started or abandonedwebhooks.part- part mounted or dismantledwebhooks.finalize- vehicle completedwebhooks.salvage- wreck strippedwebhooks.premium- unlocks, uses, paid swapswebhooks.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
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 themdescription- one line of flavour shown in the panelclass-'automobile','bike','boat','heli','plane'or'trailer'tier- 1 to 3, used for sorting and the skill gatechassis- the per-vehicle chassis item, used byConfig.ChassisMode = 'items'blueprint-false, or a blueprint item also required to start the projectmodel- 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 namepartItems- per-type item overrides for this vehicledonorOnly- buildable only by players holding that vehicle's unlock, enforced while the paid layer is onswapCost,instantCost- uses spent on this vehicle by the paid actionscounts- fixed slot counts, for examplecounts = { wheel = 4, door = 0 }. Overrides what the model reports, and is the way to allow a doorless build past the per-class minimums
Paid layer
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 runsredeemCommand- command that opens the player screen with their balance, their unlocks and the redeem box. Setfalseto hide itactions.swap- convert a project into another vehicle keeping the parts already installedactions.instantFinish- complete a project with no partsactions.*.cost- how many uses one action spendspackages- store package id mapped to what it grants.usesadds to the balance,unlockpluschassisgrant 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.
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.