Configuration
Every entry in the three config files. Defaults, types, and purpose.
Settings live in three files, all of them in escrow_ignore so they stay
editable after install:
shared/config.lua— shops, departments, vehicles, pricing, business, persistence, limits, names.shared/config.tuning.lua— custom tuning options and nitrous.shared/config.servicing.lua— service parts, wear, inspection.
Internal calibration (the mod slot map, camera presets, colour palette,
validation limits, tablet geometry) lives in shared/constants.lua and
is not part of this page.
General
Config.Frameworkstringdefault: 'auto'Which framework to use. 'auto' detects it at boot and keeps retrying until one is running. Set 'esx', 'qbcore' or 'qbox' to pin it.
Config.Localestringdefault: 'fi'Language for the panel, notifications and menus. Shipped: 'de', 'en', 'es', 'fi', 'fr', 'it', 'nl', 'pl', 'pt-br', 'tr'. Missing keys fall back to English.
Config.Debugbooleandefault: falseVerbose lifecycle and session logging to the server console and the client console. Leave off in production.
Config.DevCommandsbooleandefault: falseRegisters the developer subcommands of /mechanic. They also require an admin group, and the client only registers them once the server has confirmed one. Leave off on a live server.
Config.AdminGroupsstring[]default: { 'admin', 'superadmin', 'god', 'owner' }Framework groups treated as admin. Gates the shop creator and the developer subcommands. This resource uses no ACE permissions, so this is the one place to grant access.
Departments
A department shop is free and job-gated. Its users come from this table.
Config.Jobstabledefault: { police = { label = 'Police', minGrade = 0, defaultsMinGrade = 3 }, … }Jobs that may use department shops, keyed by job name. Each entry takes label (display name), minGrade (lowest grade that may open a department workshop) and defaultsMinGrade (lowest grade that may save a build as the department default for a model).
Config.RequireDutybooleandefault: falseOn QBCore and QBox, also require the job's on-duty flag before a department workshop opens.
Config.DefaultPrioritystring[]default: {}Order departments are searched in when Config.Persistence.crossDepartmentDefaults lets a vehicle borrow another department's default build. Empty means the Config.Jobs keys sorted alphabetically, so the answer is the same after every restart.
Shops
Every workshop is an entry in Config.Shops. A vehicle has to sit inside
one of the shop's bays before the workshop opens on it.
Config.Shopstable[]The shops on your map. See the field reference below. Overlapping bays are allowed: the first shop the player may use wins, so two departments can share one garage.
| Field | Type | Default | What it does |
|---|---|---|---|
id | string | shop<index> | Unique. Used as the database key, so do not rename one that has history. |
label | string | the id | Name shown in the panel header and on menus. |
type | string | 'department' | 'department' or 'commercial'. Anything else is refused with a console line and the shop is skipped, so a typo cannot open a free workshop to the public. |
access | string | 'self-service' | Commercial shops only. 'self-service': the customer drives the panel and pays there. 'mechanic': only staff open it and the customer approves the charge. An invalid value falls back to 'self-service'. Department shops are always free and job-gated. |
install | string | 'tablet' | 'tablet' installs from the panel with no world interaction. 'full' acts the work out: hood, hoist, wheels off, a progress bar per job and a skill check. Unknown values fall back to 'tablet'. |
job | string | none | The staff job of a commercial shop, and the society account that receives its income. Required when access is 'mechanic'; without it the shop is skipped. |
jobs | string[] | { job } | Which jobs may use a department shop. Also decides who sees the blip. |
blip | table | none | { sprite, colour, scale }, plus an optional coords. Without coords the blip sits on the first bay. |
bays | table[] | {} | { coords = vector3, size = vector3, rotation = number }. size is the full width, length and height; rotation is in degrees. |
lifts | table[] | {} | { coords = vector3, heading = number }. One car lift per entry, staff only. |
dyno | vector3 | none | Where the dyno stands. Omit to leave the shop without one. |
price | number | none | Puts the shop up for sale in game at this price. Omit and it cannot be bought. |
Owner, managers and per-category prices are not config fields. They live
in nx_mechanic_shops and are set in game, so a purchase or a price
change takes effect for everyone without a restart. Anything you place
with /mechanic creator is stored there
too.
Config.Interaction.useTargetbooleandefault: trueAdds an ox_target option to a vehicle while it sits in a bay.
Config.Interaction.targetDistancenumberdefault: 3.0Range of that target option, in metres.
Config.Interaction.seatedPromptbooleandefault: trueAlso give the driver a key prompt while sitting in a bay.
Config.Interaction.seatedKeystringdefault: 'E'Default key for the seated prompt. It is registered as the keybind nx_mechanic_open, so players can rebind it under Settings, Key Bindings, FiveM. The resource does not disable the horn, so on the default binding a press both honks and opens the panel.
Eligible vehicles
Config.Vehicles.classestabledefault: { [18] = true }Vehicle classes a department shop accepts. 18 is Emergency.
Config.Vehicles.modelsstring[]default: {}Extra models a department shop accepts whatever their class. A vehicle qualifies when its class is listed or its model is.
Config.Vehicles.blockedstring[]default: { 'onx_polmav', 'onx_polmav2', 'onx_polcon', 'polmav' }Never eligible anywhere, whatever the class says. Helicopters have no workshop.
Config.Vehicles.commercialBlockedClassestabledefault: { [13]=true, [14]=true, [15]=true, [16]=true, [18]=true, [21]=true }Classes a commercial shop refuses. Commercial shops take everything else, so the class and model lists above do not apply to them. Shipped blocked: cycles, boats, helicopters, planes, emergency, trains.
Config.Vehicles.perJobtabledefault: {}Optional per-job restriction. Keyed by job name, each entry a list of models. A job with an entry may only work on those models.
Workshop features
Each switch is enforced on the server, so a client cannot re-enable one you turned off.
Config.Features.performancebooleandefault: trueEngine, brakes, transmission, suspension, armour and turbo mod slots.
Config.Features.interiorbooleandefault: trueInterior mod slots plus interior and dashboard colours.
Config.Features.platesbooleandefault: truePlate style and window tint.
Config.Features.servicebooleandefault: trueThe repair and wash buttons.
Config.Features.neonbooleandefault: trueNeon strips and neon colour.
Config.Features.stancebooleandefault: trueCamber, track width, ride height and wheel size and width.
Config.Features.departmentDefaultbooleandefault: trueThe 'set as department default' action for grades at or above defaultsMinGrade.
Stance
Config.Stance.tickMsnumberdefault: 0How often stanced vehicles nearby are re-asserted. 0 means every frame while one is within distance. Nothing runs when none is near.
Config.Stance.distancenumberdefault: 80.0Metres. Stanced vehicles further away than this are left alone.
Config.Stance.limitstabledefault: { camber = 12.0, track = 12.0, height = 12.0, size = 20.0, width = 20.0 }Slider ranges. Camber is in degrees, track and height in centimetres, size and width as a percentage of the fitted wheel.
Wheel size and width only take effect on aftermarket wheels. On stock wheels those two sliders are disabled and the panel says why.
Share codes
A share code hands one build to another workshop as an eight-character
string, with no 0, O, 1 or I so it survives being read out over
the radio.
Config.Codes.enabledbooleandefault: trueAllow builds to be shared and imported as codes.
Config.Codes.expireDaysnumberdefault: 30How long a code lasts. 0 means codes never expire. Expiry is worked out from when the code was made, so changing this re-dates every code you already handed out.
Config.Codes.crossDepartmentbooleandefault: falseAllow importing a code created by another department.
Config.Codes.sameModelOnlybooleandefault: falseAllow importing only into the model the code was made from.
Live previews
All three are undone when the session ends, however it ends.
Config.Previews.hornbooleandefault: truePreviewing a horn option sounds it once.
Config.Previews.lightsbooleandefault: trueThe Lights category turns the headlights on while it is open.
Config.Previews.doorsbooleandefault: trueThe hood opens for Performance and the driver door for Interior.
Tablet
Config.Tablet.enabledbooleandefault: trueThe mechanic holds a tablet while the panel is open, and its screen lists the changes made so far. Everyone nearby sees the same screen. The screen needs nx-3d; without it the prop and the animation still play.
Pricing
Commercial shops only. Department shops are always free.
Config.Pricing.modestringdefault: 'fixed''fixed' charges the category's price. 'percent' charges that percentage of the vehicle's value. 'combined' adds both.
Config.Pricing.currencystringdefault: 'EUR'ISO currency code used to format prices in the panel and in notifications.
Config.Pricing.payWithstring[]default: { 'bank', 'cash' }Accounts a customer may pay from. The first is the default.
Config.Pricing.vehicleValueFallbacknumberdefault: 50000Vehicle value used for percent pricing when no vehicle shop table knows the model.
Config.Pricing.vehicleValuestabledefault: {}Model to value. Overrides every other source, including the framework's own vehicle price.
Config.Pricing.categoriestablePrice per category. Each entry takes price (fixed), percent (share of vehicle value) and optionally levelMult. See the table below.
| Category | price | percent | Notes |
|---|---|---|---|
performance | 2500 | 1.0 | levelMult = true: the price is multiplied by the level plus one, so a stage 3 engine costs four times the base. |
body | 800 | 0.3 | |
wheels | 1200 | 0.4 | |
paint | 900 | 0.3 | |
liveries | 600 | 0.2 | |
lights | 700 | 0.25 | |
interior | 500 | 0.15 | |
plates | 300 | 0.1 | |
equipment | 800 | 0.3 | Slots a vehicle pack has repurposed for kit: ram bars, spotlights, radar. |
stance | 1500 | 0.5 | Charged once per save that changes the stance. Returning to stock is free. |
Config.Pricing.slotstabledefault: { [18] = { price = 4000, percent = 1.5 } }Per mod slot price override, keyed by slot number. Slot 18 is the turbo.
Config.Pricing.markupCapnumberdefault: 1.5How far a shop owner may raise a category price in game, as a multiple of the configured price. They may always go down to zero.
Config.Pricing.servicetabledefault: { repair = 1500, wash = 150 }Fixed prices for the two service buttons.
Prices are worked out on the server by comparing what the car wears now against what it wore when the session opened. Changing something and changing it back costs nothing, and the client never sends a price.
Business
Config.Business.societystringdefault: 'auto'Where a commercial shop's income goes. 'auto' picks the first installed banking adapter; a name from bridge/society uses that one; 'builtin' keeps the balance in nx_mechanic's own table.
Config.Business.billingstringdefault: 'auto'How a mechanic invoices a customer. 'auto' picks the first installed billing adapter; a name from bridge/billing uses that one; 'builtin' uses nx_mechanic's own invoice book; 'off' hides invoicing entirely.
Config.Business.ordersbooleandefault: trueSelf-service customers may leave a work order for staff instead of paying now. Needs the shop to name a job.
Config.Business.employeesstringdefault: 'framework'Who counts as staff of a commercial shop. 'framework': anyone holding the shop's job. 'builtin': only the roster hired through the tablet. Owners and managers always count.
Config.Business.ownablebooleandefault: trueCommercial shops that carry a price can be bought, sold and transferred in game. Selling returns 60% of the purchase price.
Config.Business.inventorystringdefault: 'auto'Which inventory holds the parts. 'auto' picks the first installed one; a name from bridge/inventory uses that one; 'none' means no inventory at all. There is no builtin: with 'none' the resource holds no items and Config.Tuning.requireItems cannot be satisfied.
Management of a shop covers hiring, prices and the ledger. It is open to
the owner, to a manager they named, and to a framework grade of 3 or
higher in the shop's job. Setting
employees = 'builtin' drops the grade route, so only the owner and the
managers they name can manage.
Install flow
Applies to shops with install = 'full'. It is roleplay pacing, not a
security gate.
Config.Install.secondstabledefault: { engine = 14, tuning = 8, wheels = 6, stance = 6, servicing = 5, nitrous = 6, cosmetic = 3 }How long each kind of job takes, in seconds. A cart with nothing but cosmetic changes gets the single cosmetic job.
Config.Install.skillChecktabledefault: { engine = true, tuning = true, wheels = false, stance = false, servicing = false, nitrous = true, cosmetic = false }Which job kinds ask for a skill check. Which minigame runs is decided by the skill-check adapters.
Config.Install.liftHeightnumberdefault: 1.4Metres a raised car lift lifts the car.
Dyno
Config.Dyno.enabledbooleandefault: trueOffer dyno runs at shops that have a dyno coordinate.
Config.Dyno.runSecondsnumberdefault: 12Length of a run.
Config.Dyno.pricenumberdefault: 250What a run costs at a commercial shop.
A run reads the handling the car is actually driving with: its own base, times its tuning, times its wear. A worn or tuned car shows on the graph. The last two runs are kept per plate for a before and after.
Paint
Config.Paint.chameleonbooleandefault: trueOffer the 62 chameleon paints. They need game build 2612 or newer. Turning this off hides them from the picker; a build that already carries one still shows its name.
Config.Paint.favouritesnumber[]default: { 127, 111, 0, 12, 134, 4, 88 }Colour indices pinned at the top of the paint picker.
Colours are listed under the game's own name, grouped by family and ordered by finish inside a family. The names are not translated, so the list never shows two rows with the same label.
Names
The game ships no name for a vehicle extra, and packs sometimes ship a slot or an option without one either. These three tables are how you fill the gaps.
All three are keyed by model name and share one lookup: matching is
case-insensitive, a key ending in * is a prefix (['onx_pol*']), the
longest matching prefix wins, and ['*'] applies to any model with no
entry of its own. A value may be plain text or a locale key. A name the
pack itself ships always wins over Config.SlotLabels and
Config.ModLabels.
Config.ExtraLabelstableNames for vehicle extras, keyed by model then extra index. Without an entry an extra shows as 'Extra N'. The game has no name for an extra, so this is the only source.
Config.SlotLabelstableNames for mod slots the vehicle pack left unnamed, keyed by model then slot number. The shipped default names slots 42 to 45 as callsign digits, which is what vanilla pursuit cars and several emergency packs use them for.
Config.ModLabelstableNames for the options inside a slot, keyed by model, then slot, then option index counting from zero. Use it when a pack ships a label with no text behind it.
Run /mechanic dump on a vehicle to
see, per slot and per option, exactly what the pack shipped and whether
it resolves to any text.
Limits
Config.Limits.openDistancenumberdefault: 8.0Metres. How close the player must be to open the workshop.
Config.Limits.sessionDistancenumberdefault: 12.0Metres. Leaving this range ends the session.
Config.Limits.saveCooldownMsnumberdefault: 1500Minimum gap between saves and service actions.
Config.Limits.sessionTimeoutMinnumberdefault: 15Minutes. An idle session is ended by the server after this, and the vehicle is handed back.
Camera
Config.Camera.centerOffsetnumberdefault: 0.10Fraction of the screen width the camera's aim is shifted right, so the vehicle sits centred in the space the panel leaves free. 0 centres it on screen.
Config.Camera.peekKeystringdefault: 'MOUSE_RIGHT'Hold to hide the panels and orbit the camera freely. Releasing returns to where the camera was.
Config.CameraOverridestabledefault: {}Per-model camera framing, merged over the built-in presets. Keyed by model, then focus name: overview, front, rear, side, sideR, roof, top, wheel, interior, engine. Each is { az = degrees, el = degrees, dist = multiple of the vehicle's size }.
Persistence
Builds always live in nx_mechanic_* tables. On top of that, every save
is merged into the props column of the framework vehicle tables you list
here, so garages respawn the vehicle with the build applied. Tables that
do not exist on your server are skipped with a console note.
Config.Persistence.synctable[]default: { { table = 'owned_vehicles', plate = 'plate', props = 'vehicle' }, { table = 'player_vehicles', plate = 'plate', props = 'mods' }, { table = 'sky_jobs_vehicles', plate = 'plate', props = 'vehicle_mods' } }Framework tables that also receive each save. Each entry names the table, its plate column and its props column. The shipped three cover ESX, QBCore and QBox, and sky_jobs_base.
Config.Persistence.applyOnEnterbooleandefault: trueWhen a driver gets into an eligible vehicle, apply the plate's build, or the department default for the model if it has none.
Config.Persistence.trackUnownedbooleandefault: falseAlso keep a vehicle record for plates no framework table knows about, such as random spawns. Off means only owned or department vehicles carry tuning, wear and stance across a respawn.
Config.Persistence.crossDepartmentDefaultsbooleandefault: falseWhen the driver's own department has no default for the model, borrow another department's. The order is Config.DefaultPriority.
Config.Persistence.slowPlateMatchbooleandefault: falseMatch rows in the sync tables with a full-table string comparison instead of the indexed plate lookup. Only turn this on if your plates are stored in a shape the indexed match misses.
Config.Persistence.logRetentionDaysnumberdefault: 90Audit rows older than this are pruned at start. 0 keeps them forever.
Any resource that changes a vehicle's plate must call
PlateChanged, or the
vehicle's record, build and history stay on the old plate.
Logs
Events are grouped into categories: sales, orders, tuning,
servicing, inspection, shop and security.
Config.Logs.consoletabledefault: { security = true }Which categories are printed to the server console. Set it to true instead of a table to print every category. Security events are always printed.
Config.Logs.oxlibbooleandefault: falseAlso send events through ox_lib's logger.
Config.Logs.discordtabledefault: { url = '', categories = { sales = true, orders = true, shop = true, security = true } }Discord webhook URL and the categories sent to it. An empty URL disables it. A webhook Discord rejects is reported once and then left alone.
Notifications
Config.Notify.titlestringdefault: 'NX Mechanic'Title shown on the resource's notifications.
Tuning
In shared/config.tuning.lua. Every option scales the vehicle's own
stored handling rather than replacing it, so an add-on car keeps its
character. A value of 1.20 means twenty percent more than this car
already has. Absolute values are written as
{ mode = 'set', value = x }, and { mode = 'add', value = x } adds one.
Config.Tuning.enabledbooleandefault: trueMaster switch for custom tuning.
Config.Tuning.selfServicebooleandefault: falseAlso offer tuning in self-service shops. Off means mechanic shops only.
Config.Tuning.requireItemsbooleandefault: falseThe mechanic must be carrying the option's item, and it is consumed on install. Needs an inventory; see Config.Business.inventory and Parts as items below.
Config.Tuning.soundbooleandefault: trueAn engine swap changes how the car sounds. Each engine's sound field is any vehicle model name.
Config.Tuning.blacklisttabledefault: { models = {}, classes = { [13]=true, [14]=true, [15]=true, [16]=true, [21]=true } }Never tunable, whatever the shop offers. Shipped blocked: cycles, boats, helicopters, planes and trains.
Each option in the lists below takes an id, a label, a price, an
item, an optional sound (engines only), an optional restricted
('combustion' or 'electric', which hides the option on the other kind
of car) and a handling table of scaling factors.
Config.Tuning.enginestable[]Engine swaps. Shipped: I4 Turbo 2.5L (25,000), V6 3.3L (35,000), V8 6.5L (55,000), V12 6.0L (85,000), all combustion only, and a performance motor (60,000) for electric cars.
Config.Tuning.drivetrainstable[]All-wheel drive (12,000), rear-wheel drive (9,000), front-wheel drive (9,000). Each sets the drive bias outright rather than scaling it.
Config.Tuning.turbostable[]Turbocharger (15,000), combustion only.
Config.Tuning.gearboxestable[]Close-ratio gearbox (14,000) shifts faster; long-ratio gearbox (14,000) trades acceleration for top speed.
Config.Tuning.tyrestable[]Sport (3,500), semi-slick (6,000), slick (9,500), off-road (4,500) and drift (5,000).
Config.Tuning.brakestable[]Sport brakes (6,000) and ceramic brakes (14,000).
Config.Tuning.drifttable[]Drift tuning kit (18,000): less grip, more lateral hold and more steering lock.
Removing an option is free, and a category is only charged when it
actually changes. Tuning is stored per plate, so it needs a vehicle
record. A plate the tracking policy does not keep cannot be tuned. See
Config.Persistence.trackUnowned.
Nitrous
Also in shared/config.tuning.lua. A kit is installed once, a bottle
holds a number of shots, and each press fires one. The shot count is held
by the server, so pressing the key faster does not buy more boost.
Config.Nitrous.enabledbooleandefault: trueMaster switch for nitrous.
Config.Nitrous.keystringdefault: 'LMENU'Fire key. Registered as the keybind nx_mechanic_nos, so players can rebind it in FiveM's key settings.
Config.Nitrous.purgeKeystringdefault: 'X'Purge key. A purge clears the line with a coloured cloud. Registered as the keybind nx_mechanic_purge, so it is rebindable too.
Config.Nitrous.shottabledefault: { seconds = 3.0, power = 1.0 }How long one shot lasts and how hard it pushes. Power multiplies the game's own boost.
Config.Nitrous.cooldownMsnumberdefault: 4000Minimum gap between shots on one vehicle, enforced on the server.
Config.Nitrous.purgeWindowSecnumberdefault: 20A purge this recent makes the next shot safe.
Config.Nitrous.engineRisktabledefault: { chance = 0.15, damage = 120.0 }A shot fired without a recent purge has this chance of costing that much engine health. The roll happens on the server.
Config.Nitrous.kittabledefault: { price = 12000, item = 'nxm_nos_kit' }Price of the kit, charged once, and the item it needs when Config.Tuning.requireItems is on.
Config.Nitrous.bottlestable[]Bottle sizes. Each entry takes id, label, shots, price and item. Shipped: small (5 shots, 2,500), medium (10 shots, 4,500), large (20 shots, 8,000). A new bottle arrives full and is charged when it changes.
Config.Nitrous.refillPerShotnumberdefault: 350Cost of topping up one missing shot.
Config.Nitrous.purgeColourstable[]Colours a driver may pick for the purge cloud. Each entry takes id, label and rgb. Shipped: white, blue, red, green, purple, pink. Changing the colour is free.
Config.Nitrous.blockedClassestabledefault: { [8]=true, [13]=true, [14]=true, [15]=true, [16]=true, [21]=true }Classes that cannot fit nitrous: motorcycles, cycles, boats, helicopters, planes and trains. Electric cars are excluded as well.
Servicing
In shared/config.servicing.lua. Parts wear with distance and with how
the car is driven. A worn part scales the car's own handling down towards
its floor, and at zero percent it fails outright.
Config.Servicing.enabledbooleandefault: trueMaster switch for servicing and wear.
Config.Servicing.thresholdnumberdefault: 25Percentage at or below which a part counts as due for service, in the inspection and on the tablet.
Config.Servicing.failWhenWornbooleandefault: trueParts at zero percent cause real failures: stalls, smoke, brake fade, misfires. Turn off to keep only the handling penalty.
Config.Servicing.highRevstabledefault: { above = 0.85, multiplier = 1.6 }Wear multiplier while the engine is above that share of its rev range.
Config.Servicing.boostnumberdefault: 1.4Extra multiplier while a turbo is fitted and the revs are high.
Config.Servicing.nitrousnumberdefault: 2.5Multiplier while a nitrous shot is running.
Config.Servicing.collisiontabledefault: { minSpeedKmh = 40, cooldownMs = 5000, parts = 2, minPct = 5, maxPct = 15 }Crash damage. A collision above minSpeedKmh damages that many parts by between minPct and maxPct, no more often than cooldownMs. The server picks which parts and by how much.
Config.Servicing.conflictingstring[]default: { 'RealisticVehicleFailure', 'qb-vehiclefailure', 'qbx_vehiclefailure' }Resources whose own damage system fights this one. While one of them is running the wear loop stays off and both consoles say so.
Config.Servicing.allowConflictingbooleandefault: falseRun the wear loop anyway, alongside a conflicting resource. Expect the two to fight over the same car.
Config.Servicing.partstable[]The parts themselves. Add, rename or remove them freely: the panel and the wear loop both read this list. See the table below.
id | Label | lifespanKm | price | qty | Fits | Fails as |
|---|---|---|---|---|---|---|
oil | Engine oil | 3,000 | 450 | 1 | combustion | engine |
oilFilter | Oil filter | 5,000 | 250 | 1 | combustion | engine |
airFilter | Air filter | 8,000 | 200 | 1 | combustion | misfire |
sparkPlugs | Spark plugs | 15,000 | 320 | 4 | combustion | misfire |
brakePads | Brake pads | 10,000 | 600 | 1 | any | brakes |
brakeFluid | Brake fluid | 15,000 | 180 | 1 | any | brakes |
clutch | Clutch | 25,000 | 1,200 | 1 | combustion | clutch |
suspension | Suspension | 30,000 | 1,500 | 1 | any | none |
tyres | Tyres | 20,000 | 800 | 4 | any | tyres |
coolant | Coolant | 12,000 | 150 | 1 | combustion | overheat |
battery | Battery pack | 40,000 | 6,000 | 1 | electric | stall |
motor | Drive motor | 60,000 | 8,000 | 1 | electric | stall |
qty is how many of the part's item a service consumes when
Config.Tuning.requireItems is on. Each part also carries a handling
table: the values the car's own handling is scaled towards as the part
wears out.
Inspection
Config.Inspection.enabledbooleandefault: trueOffer roadworthiness inspections at mechanic shops.
Config.Inspection.pricenumberdefault: 600What an inspection costs.
Config.Inspection.validDaysnumberdefault: 365How long a passing certificate stays valid.
Config.Inspection.minEngineHealthnumberdefault: 600Lowest engine health that passes, out of 1000.
Config.Inspection.minBodyHealthnumberdefault: 700Lowest body health that passes, out of 1000.
Config.Inspection.illegaltabledefault: { windowTint = { 1, 2 }, neon = true, nitrous = true, tuning = { 'drift' } }What fails a road-legal inspection: window tint indices 1 (pure black) and 2 (dark smoke), any neon, a nitrous kit, and the drift tuning category.
Config.Inspection.policeJobsstring[]default: { 'police', 'sheriff' }Jobs that may look a certificate up with /inspection.
An inspection runs six checks:
| Check | Passes when |
|---|---|
engine | Engine health is at least minEngineHealth and engine oil is above Config.Servicing.threshold. |
body | Body health is at least minBodyHealth. |
tyres | No tyre is burst and tyre wear is above Config.Servicing.threshold. |
brakes | Brake pads are above Config.Servicing.threshold and brake fluid is above zero. |
lights | The headlights are not broken. |
legal | Nothing on the illegal list is fitted. |
One failed check fails the inspection. The result is stored against the
plate; other resources read it through
GetInspection.
Adapters
Five kinds of adapter sit in bridge/. Every file there stays readable
and editable after install, so a system that is not on these lists is a
short file away.
Each kind resolves lazily and re-checks while nothing has been found, so
a resource that starts after nx_mechanic is still picked up without a
restart. Whichever adapter is in use is printed to the server console at
the first call.
Society
Where a commercial shop's takings go. bridge/society/.
Detection order:
nx_realbanking, Renewed-Banking, fd_banking, okokBanking,
qb-banking, qb-management, tgg-banking, wasabi_banking,
esx_addonaccount, then the built-in ledger.
An adapter registers itself and implements three calls:
NXM.Society.Register('my_banking', {
resource = 'my_banking', -- must be started for auto-detection
frameworks = { 'esx' }, -- optional: esx | qbcore | qbox
GetBalance = function(job) return 0 end, -- nil when unknown
Add = function(job, amount, reason) return true end,
Remove = function(job, amount, reason) return true end, -- false when short
})job is the framework job name. An adapter whose banking resource wants
society_<job> adds that prefix itself. Amounts are positive whole
numbers. Copy the shortest file in the folder, change the name, the
resource and the three calls.
Billing
How a mechanic invoices a customer. bridge/billing/.
Detection order:
nx_realbanking, okokBilling, codem-billing, esx_billing,
qb-billing, qs-billing, 17mov_billing, wasabi_billing,
Renewed-Banking, then the built-in invoice book.
NXM.Billing.Register('my_billing', {
resource = 'my_billing',
frameworks = { 'esx' },
Create = function(inv)
-- inv carries: job, jobLabel, shopId, shopLabel,
-- mechanicSrc, mechanicIdentifier, mechanicName,
-- customerSrc, customerIdentifier, customerName,
-- amount, label, lines, plate, model
return true, 'REF-123' -- or false, 'SHORT_REASON'
end,
})Return the billing resource's own reference when it hands one back, and an empty string when it does not. A failure reason is logged, never shown to a player.
Inventory
Which inventory holds the parts. bridge/inventory/.
Detection order:
ox_inventory, qb-inventory, qs-inventory, codem-inventory,
core_inventory, then the framework's own pockets on ESX and QBCore.
NXM.Items.Register('my_inventory', {
resource = 'my_inventory',
Has = function(src, item, count) return true end,
Count = function(src, item) return 0 end,
Add = function(src, item, count, metadata) return true end,
Remove = function(src, item, count) return true end, -- false when short
})Add the name to the ORDER list in bridge/inventory/init.lua so
auto-detection can find it. There is no built-in fallback here: with no
inventory, nothing has an item, and Config.Tuning.requireItems cannot
be satisfied.
Item names are passed through unchanged. Nothing remaps them, so the
names in shared/config.tuning.lua are the names your inventory needs.
Skill checks
bridge/skillcheck/, client side. ox_lib and ps-ui ship, plus
none, which always passes. Detection order is ox_lib, ps-ui,
none.
NXM.SkillCheck.Register('my_minigame', {
resource = 'my_minigame',
Run = function(kind) return true end, -- kind: 'engine', 'tuning', 'nitrous', …
})Handling capture guards
bridge/handling/, client side. A vehicle's untouched handling is read
once per plate and everything else is layered over it. A guard defers
that first read while another resource has the car's handling rewritten,
so a wet road never becomes a car's idea of stock.
One adapter ships, for nx_fourseasons, whose road effects change grip
live.
NXM.Handling.AddCaptureGuard(function(vehicle)
-- Return a short reason to defer the read, or nil to allow it.
return nil
end)Parts as items
Only needed when
Config.Tuning.requireItems
is on. It ships off, and while it is off none of this applies.
Nineteen items, one per tuning option. Weights are in grams and every part is unstackable, so a mechanic carrying two turbos carries two slots' worth.
| Item | Label | Weight | List |
|---|---|---|---|
nxm_engine_i4t | I4 Turbo 2.5L | 5000 | engines |
nxm_engine_v6 | V6 3.3L | 5000 | engines |
nxm_engine_v8 | V8 6.5L | 5000 | engines |
nxm_engine_v12 | V12 6.0L | 5000 | engines |
nxm_motor_ev | Performance motor | 5000 | engines |
nxm_drivetrain_awd | All-wheel drive | 5000 | drivetrains |
nxm_drivetrain_rwd | Rear-wheel drive | 5000 | drivetrains |
nxm_drivetrain_fwd | Front-wheel drive | 5000 | drivetrains |
nxm_turbo | Turbocharger | 3000 | turbos |
nxm_gearbox_close | Close-ratio gearbox | 5000 | gearboxes |
nxm_gearbox_long | Long-ratio gearbox | 5000 | gearboxes |
nxm_tyres_sport | Sport tyres | 1500 | tyres |
nxm_tyres_semi | Semi-slick tyres | 1500 | tyres |
nxm_tyres_slick | Slick tyres | 1500 | tyres |
nxm_tyres_offroad | Off-road tyres | 1500 | tyres |
nxm_tyres_drift | Drift tyres | 1500 | tyres |
nxm_brakes_sport | Sport brakes | 1500 | brakes |
nxm_brakes_ceramic | Ceramic brakes | 1500 | brakes |
nxm_drift_kit | Drift tuning kit | 3000 | drift |
Service parts, nitrous kits and nitrous bottles carry item names too. See
the item field on each entry in Config.Servicing.parts and
Config.Nitrous.
ox_inventory
Paste into ox_inventory/data/items.lua, inside the returned table.
Images go in ox_inventory/web/images/ as <item name>.png; without one
the slot shows the fallback icon.
['nxm_engine_i4t'] = { label = 'I4 Turbo 2.5L', weight = 5000, stack = false, close = true },
['nxm_engine_v6'] = { label = 'V6 3.3L', weight = 5000, stack = false, close = true },
['nxm_engine_v8'] = { label = 'V8 6.5L', weight = 5000, stack = false, close = true },
['nxm_engine_v12'] = { label = 'V12 6.0L', weight = 5000, stack = false, close = true },
['nxm_motor_ev'] = { label = 'Performance motor', weight = 5000, stack = false, close = true },
['nxm_drivetrain_awd'] = { label = 'All-wheel drive', weight = 5000, stack = false, close = true },
['nxm_drivetrain_rwd'] = { label = 'Rear-wheel drive', weight = 5000, stack = false, close = true },
['nxm_drivetrain_fwd'] = { label = 'Front-wheel drive', weight = 5000, stack = false, close = true },
['nxm_turbo'] = { label = 'Turbocharger', weight = 3000, stack = false, close = true },
['nxm_gearbox_close'] = { label = 'Close-ratio gearbox', weight = 5000, stack = false, close = true },
['nxm_gearbox_long'] = { label = 'Long-ratio gearbox', weight = 5000, stack = false, close = true },
['nxm_tyres_sport'] = { label = 'Sport tyres', weight = 1500, stack = false, close = true },
['nxm_tyres_semi'] = { label = 'Semi-slick tyres', weight = 1500, stack = false, close = true },
['nxm_tyres_slick'] = { label = 'Slick tyres', weight = 1500, stack = false, close = true },
['nxm_tyres_offroad'] = { label = 'Off-road tyres', weight = 1500, stack = false, close = true },
['nxm_tyres_drift'] = { label = 'Drift tyres', weight = 1500, stack = false, close = true },
['nxm_brakes_sport'] = { label = 'Sport brakes', weight = 1500, stack = false, close = true },
['nxm_brakes_ceramic'] = { label = 'Ceramic brakes', weight = 1500, stack = false, close = true },
['nxm_drift_kit'] = { label = 'Drift tuning kit', weight = 3000, stack = false, close = true },qb-core
Paste into qb-core/shared/items.lua, inside the QBShared.Items table.
unique = true is the qb-core equivalent of stack = false. Images go in
your inventory resource's html/images/. ps-inventory and lj-inventory
read the same table.
['nxm_engine_i4t'] = { name = 'nxm_engine_i4t', label = 'I4 Turbo 2.5L', weight = 5000, type = 'item', image = 'nxm_engine_i4t.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_engine_v6'] = { name = 'nxm_engine_v6', label = 'V6 3.3L', weight = 5000, type = 'item', image = 'nxm_engine_v6.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_engine_v8'] = { name = 'nxm_engine_v8', label = 'V8 6.5L', weight = 5000, type = 'item', image = 'nxm_engine_v8.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_engine_v12'] = { name = 'nxm_engine_v12', label = 'V12 6.0L', weight = 5000, type = 'item', image = 'nxm_engine_v12.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_motor_ev'] = { name = 'nxm_motor_ev', label = 'Performance motor', weight = 5000, type = 'item', image = 'nxm_motor_ev.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_drivetrain_awd'] = { name = 'nxm_drivetrain_awd', label = 'All-wheel drive', weight = 5000, type = 'item', image = 'nxm_drivetrain_awd.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_drivetrain_rwd'] = { name = 'nxm_drivetrain_rwd', label = 'Rear-wheel drive', weight = 5000, type = 'item', image = 'nxm_drivetrain_rwd.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_drivetrain_fwd'] = { name = 'nxm_drivetrain_fwd', label = 'Front-wheel drive', weight = 5000, type = 'item', image = 'nxm_drivetrain_fwd.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_turbo'] = { name = 'nxm_turbo', label = 'Turbocharger', weight = 3000, type = 'item', image = 'nxm_turbo.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_gearbox_close'] = { name = 'nxm_gearbox_close', label = 'Close-ratio gearbox', weight = 5000, type = 'item', image = 'nxm_gearbox_close.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_gearbox_long'] = { name = 'nxm_gearbox_long', label = 'Long-ratio gearbox', weight = 5000, type = 'item', image = 'nxm_gearbox_long.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_tyres_sport'] = { name = 'nxm_tyres_sport', label = 'Sport tyres', weight = 1500, type = 'item', image = 'nxm_tyres_sport.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_tyres_semi'] = { name = 'nxm_tyres_semi', label = 'Semi-slick tyres', weight = 1500, type = 'item', image = 'nxm_tyres_semi.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_tyres_slick'] = { name = 'nxm_tyres_slick', label = 'Slick tyres', weight = 1500, type = 'item', image = 'nxm_tyres_slick.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_tyres_offroad'] = { name = 'nxm_tyres_offroad', label = 'Off-road tyres', weight = 1500, type = 'item', image = 'nxm_tyres_offroad.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_tyres_drift'] = { name = 'nxm_tyres_drift', label = 'Drift tyres', weight = 1500, type = 'item', image = 'nxm_tyres_drift.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_brakes_sport'] = { name = 'nxm_brakes_sport', label = 'Sport brakes', weight = 1500, type = 'item', image = 'nxm_brakes_sport.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_brakes_ceramic'] = { name = 'nxm_brakes_ceramic', label = 'Ceramic brakes', weight = 1500, type = 'item', image = 'nxm_brakes_ceramic.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },
['nxm_drift_kit'] = { name = 'nxm_drift_kit', label = 'Drift tuning kit', weight = 3000, type = 'item', image = 'nxm_drift_kit.png', unique = true, useable = false, shouldClose = true, combinable = nil, description = '' },Language
Config.Locale picks the language. Ten ship: de, en, es, fi,
fr, it, nl, pl, pt-br and tr. Each is a JSON file in
locales/. English is always loaded first and the chosen language is
merged over it, so a key your language is missing falls back to English
rather than showing the raw key.
To add a language, copy locales/en.json to locales/<code>.json,
translate the values, and set Config.Locale to that code. The folder is
in escrow_ignore, so your file survives an update. Keep a copy before
updating in case the shipped keys change.
The game's own names for mod slots and options come from the vehicle itself and are not translated. Neither are colour names.