ns-vehiclehandling
In-game vehicle handling editor for FiveM by NullSect.
An admin gets into a vehicle, opens the editor, changes handling values and feels the result immediately. Values are saved into profiles stored in the database, and the active profile of a model is applied automatically every time that model spawns — including after a server restart.
Qbox is the primary framework. QBCore, ESX and standalone (ACE only) work through bridge/.
Features
/vhand/vehiclehandlingeditor (admin only, checked on both client and server)- Vehicle info: name, model, model hash, plate
- 34 real
CHandlingDatafields in 9 categories (Physics, Engine, Transmission, Brakes, Steering, Traction, Suspension, Damage, Fuel) — float, int (nInitialDriveGears) and vector (vecCentreOfMassOffset,vecInertiaMultiplier) fields, each written with the matching native - Central field registry (
shared/handling_fields.lua): the UI is generated from it and the server validates against it, so unknown fields can never reach the database - Original / Current / Difference for every field, per-field Reset, Reset all
- Live preview: values are applied the moment they change and read back from the game. Fields the game only reads when the vehicle is created (or values that do not read back) are marked Vehicle refresh required and can be applied with Apply & refresh
- Apply & refresh respawns the vehicle in place and keeps position, heading, plate, colours, mods, livery, extras, windows, doors, tyres, fuel, health and occupants
- Multiple profiles per model: New, Edit, Rename, Clone (deep copy, automatic
Race v2naming), Activate, Delete, Reset profile - Read-only Default profile = the model's original handling, created automatically; it is never modified or deleted
- One active profile per model (database transaction + unique constraint)
- Simple version history per profile with restore
- Server cache
vehicle hash -> active profile; no database query per spawn - State bag based apply: every client applies the profile, each version only once per entity
- Test drive mode (panel hides, press
F7to return) - Built-in Help page: short tuning tips (acceleration, top speed, grip, brakes, drift, durability); each tip links to the fields it talks about
- English and Turkish, all UI text from locale files
- Idle cost: one 1 s check of the local player's current vehicle; no world scanning loops
Dependencies
| Resource | Required |
|---|---|
OneSync (onesync on) | yes |
| oxmysql | yes |
| qbx_core / qb-core / es_extended | optional (admin groups, notifications) |
| ox_lib | optional (notifications) |
| qbx_vehiclekeys / qb-vehiclekeys | optional (keys after refresh) |
| LegacyFuel / ps-fuel / cdn-fuel / lj-fuel / ox_fuel | optional (fuel kept on refresh) |
The NUI uses system fonts only; clients do not need internet access.
Installation
- Put the
ns-vehiclehandlingfolder into yourresourcesdirectory. - Import
sql/vehiclehandling.sql(or keepConfig.Database.autoCreateTables = true). - Add to
server.cfgafter oxmysql and your framework:ensure oxmysql ensure qbx_core ensure ns-vehiclehandling - Give admins permission (see Permissions).
- Restart the server, get into a vehicle and type
/vh.
SQL setup
sql/vehiclehandling.sql creates two tables (MySQL 5.7+ / MariaDB 10.2+). It only uses
CREATE TABLE IF NOT EXISTS, so it never overwrites existing data.
vehicle_handling_profiles
| Column | Description |
|---|---|
id | profile id |
vehicle_model | spawn name (e.g. sultan) |
vehicle_hash | unsigned model hash |
profile_name | unique per model |
data | handling values as JSON |
version | increases on every save / reset / restore |
is_default | 1 = original handling (read-only) |
is_active | 1 = applied automatically on spawn |
created_by, updated_by | player names |
created_at, updated_at | timestamps |
active_hash, default_hash | generated columns with unique keys: at most one active and one default profile per model |
vehicle_handling_profile_versions — profile_id, version, data, created_by,
created_at. Rows are removed with their profile (ON DELETE CASCADE) and pruned to
Config.History.maxVersions.
Commands
| Command | Who | Description |
|---|---|---|
/vh, /vehiclehandling | admin | open the editor (must be the driver of a networked vehicle) |
/vhfocus (default key F7) | admin with the editor open | return from test drive |
/vhdebug cache | reload | vehicle | server console, or admin with Config.Debug = true | print the cache, reload it from the database, show the state of your vehicle |
/vhcompare | admin with Config.Debug = true | run in two vehicles of the same model (e.g. fresh spawn, then after Apply & refresh); prints every handling / effective value that differs to F8 |
ESC closes the editor (asks first when there are unsaved changes).
Permissions
Every request is checked on the server with bridge/permission.lua; the client check only avoids
opening the UI for nothing. A player who triggers the events manually gets err_no_permission and is
logged.
An admin is:
- anyone with the ACE
ns-vehiclehandling.admin:add_ace group.admin ns-vehiclehandling.admin allow - or, when
Config.Permissions.useFramework = true, anyone inConfig.Permissions.groups(Qbox:group.<name>ACE /qbx_core:HasPermission, QBCore:HasPermission, ESX:getGroup()).
Config
All options are in config.lua.
| Option | Purpose |
|---|---|
Config.Framework | auto, qbox, qbcore, esx, standalone |
Config.Locale | en or tr |
Config.Debug | debug output + enables /vhdebug for admins |
Config.Commands | command names, focus key, debug command |
Config.Permissions | ACE name, framework groups |
Config.Editor.requireDriverSeat | only the driver can open the editor |
Config.Editor.revertOnClose | remove unsaved preview values when the editor closes |
Config.AutoApply | enable auto apply, include ambient traffic, start-up scan, update existing vehicles on save |
Config.Refresh | enable Apply & refresh, give keys again, state bags copied to the new entity |
Config.Profiles | Default profile name, profiles per model, name length |
Config.History | enable history, versions kept |
Config.Database | auto create tables, table names |
Config.Security | request token bucket, cooldowns |
Config.AllowedFields | fields shown in the editor and accepted by the server |
Config.FieldRanges | per-field min / max overrides |
Field values and ranges
Values are shown and stored exactly as the handling natives return them. Some differ from
handling.meta: fSteeringLock and fTractionCurveLateral are radians, fInitialDriveMaxFlatVel
is m/s, bias fields are doubled. The ranges in the registry are wide on purpose; the server rejects
NaN, infinity, wrong types and anything outside the range. A value equal to the model's original
(Default profile) value is always accepted, even outside the range, so unusual stock vehicles still
work.
Fields marked Needs refresh (fMass, vecCentreOfMassOffset, vecInertiaMultiplier,
fInitialDriveMaxFlatVel, nInitialDriveGears, fSuspensionRaise, fPetrolTankVolume) are
written to the vehicle immediately but the game only fully uses them when the vehicle is created.
The game also caches parts of other fields at spawn and when mods are applied. Therefore the whole
active profile is also written into the model's global handling on every client, so every newly
spawned (or streamed-in) vehicle of the model is created with it — the same result as Apply &
refresh. Vehicles that already exist need Apply & refresh or a respawn. The global handling is
looked up by the handlingId from vehicles.meta; if it differs from the model name, add it to
Config.HandlingNames. Vehicles on a non-active profile or explicitly on Default are created from
the active profile and then get their own values written; spawn-cached fields stay at the active
profile's values until they are refreshed. With Config.AutoApply.includeAmbient = false, ambient
vehicles are still created from the active profile.
Profile system
- The first time a model is opened, its original handling is read from the vehicle and stored as the read-only Default profile, which becomes active.
- New starts from the Default values. Clone makes an independent copy
(
Race→Race v2). Rename / Delete / Reset profile are not available for Default. - Save writes the current values to the selected profile. The server checks permission, the profile, the model, every field, type and range, and the profile version (someone else may have saved in the meantime).
- Activate makes a profile the model's active profile inside a transaction; the previous one becomes inactive, and every spawned vehicle of that model switches to it. The active profile cannot be deleted — activate another one first.
- Apply to vehicle puts the selected (saved) profile on the vehicle you are in. Apply to existing vehicles puts it on every spawned vehicle of that model.
- Version history: every save / reset / restore creates a new version; Restore copies an old version into a new one.
- Activating Default puts every vehicle of that model (spawned and new) back on stock handling.
Auto apply
- On start the server loads all active profiles into memory (
vehicle hash → profile) and, once, puts them on already spawned vehicles that have no profile yet. entityCreatedchecks new vehicles against that cache (no database access) and sets thensvhstate bag{ id, v }. For spawn methods where that event is missing or early, the driver's client asks the server once (claim), which applies the same cached active profile.- Clients react to the state bag, fetch the profile once per version and apply it. The same version is never applied twice to the same entity; a stream-in, ownership change or client restart is covered by a one-time pass and a 1 s check of the local player's vehicle.
- Changing the active profile updates every spawned vehicle of the model and all new spawns.
Apply to existing vehicles puts a non-active profile on all spawned vehicles. Saving a profile
updates vehicles already using it (
Config.AutoApply.updateExistingOnSave). ResetVehicle/ applying Default sets the state bag tofalse: clients restore the original values and the vehicle is not picked up again by auto apply.
Exports
Server:
local profile = exports['ns-vehiclehandling']:GetActiveProfile('sultan') -- or a model hash
-- { id, name, version, isDefault, model, hash, data } | nil
exports['ns-vehiclehandling']:ApplyProfile(vehicle, profileId) -- true | false, err
exports['ns-vehiclehandling']:ResetVehicle(vehicle) -- true | false, err
Client (local only):
exports['ns-vehiclehandling']:GetActiveProfile('sultan')
exports['ns-vehiclehandling']:ApplyProfile(vehicle, profileId)
exports['ns-vehiclehandling']:ResetVehicle(vehicle)
The exports never write to the database and never bypass admin checks.
Architecture
nui/ presentation only (built from the registry, text from locales)
client/ handling natives + state bag apply (handling.lua), NUI bridge (nui.lua), editor + commands (main.lua)
bridge/ framework, notifications, fuel, keys, vehicle properties, admin check
server/ router + sessions + auto apply (main.lua), guards (security.lua), domain + cache (profiles.lua), SQL (database.lua)
shared/ config helpers, locale, handling field registry
Every client request goes through one router event (ns-vehiclehandling:sv:request) with flood
protection, per-action cooldowns, admin check, per-player lock and an editor session bound to the
vehicle the admin opened. Profile writes run under one server-wide write lock.
Kısa kurulum (TR)
- Klasörü
resourcesiçine koyun,sql/vehiclehandling.sqldosyasını içe aktarın (veyaConfig.Database.autoCreateTables = truebırakın). server.cfg:ensure oxmysql→ framework →ensure ns-vehiclehandling.- Yetki:
add_ace group.admin ns-vehiclehandling.admin allow(veya framework admin grupları). - Türkçe arayüz için
Config.Locale = 'tr'. - Araca binip
/vhyazın. Değerleri değiştirin, Kaydet, profili Aktif et — bu modelin yeni spawn olan araçlarına otomatik uygulanır.