# Page

## About

This is a multipurpose handling system that will allow you to do the finest adjustments on vehicles parameters with an easy to use user interface, developer tool and advanced Drift Mode. The system will suit any type of server, no matter if it is a roleplay (where it could be used as a tuning tablet), freeroam, drift or racing where it could serve as a utility for players to freely adjust their cars or just as a developer tool with a feature that generates an XML code to just copy and drop into your server files.

For a roleplay server it can open variety of new scenarios, making each individual vehicle feel unique. You want Ford Mustang be stable on the road. With good skills you will be able to achieve it. You want it to be as drift car? Do it!

With our unique and advanced Drift System, you can make any upgraded car slide sideways easily. On top of that each player can configure the drift mode that suits them.

{% embed url="<https://youtu.be/3pJpxc4HfWY>" %}

## Installation

Installation difficulty

🟠⚫⚫⚫⚫

{% hint style="info" %}
If you have any difficulties with resource installation you could ask for our services on our [Discord server](https://discord.gg/jwd5av9XbW) by creating a ticket.
{% endhint %}

### 1 - Add the script

* Download the resource from [cfx keymaster](https://keymaster.fivem.net/asset-grants);
* Unzip the resource archive;
* Add the script to your `resources` folder;
* Add `db_qbcore.sql` or `db_esx.sql` (depending on your framework) to your database, or do not add any sql, but in such case make sure to set `Config.UseDatabaseHandlingSaving` and Config.UseDatabasePresetsSaving to `false`;
* Add the resource to your server start file (`server.cfg`) by writing `ensure sb-handlingtuning`. Make sure it is started after any dependencies you will be using.

### 2 - Adjust configs

Adjust files found in the `/configs` folder to fit your server.

Make sure to go through all options in `/configs/config.lua` and read the commented code to adjust the config to fit your server.

Most important settings are under the `Framework` section.

### 3 - Save Standard Vehicles handlings

{% hint style="info" %}
If you change vehicles handlings in your server files make sure to repeat the step again!
{% endhint %}

This step is not necessary but highly recommended and it only needs to be done once.

* After the resource is started join the game;
* Make sure you have admin permissions for the resource;
* Make sure you have added all your addon vehicles to `Config.AddonVehicles` table in the `configs/config.lua` file before doing next step;
* Use command `/fetchstandard` in client side and wait until all vehicles are loaded (you will see an amount of them increasing);

If you will not do this, the script might set incorrect values to vehicles and show incorrect values in the tabled UI.\
If you will not add all your addon vehicles models to `Config.AddonVehicles` script will try to do that automatically when car is spawned, but it might aswell corrupt the data.

## Config

{% tabs %}
{% tab title="config.lua" %}

```lua
Config = {}

-- 88888888b                                                                    dP       
-- 88                                                                           88       
-- a88aaaa    88d888b. .d8888b. 88d8b.d8b. .d8888b. dP  dP  dP .d8888b. 88d888b. 88  .dP  
-- 88        88'  `88 88'  `88 88'`88'`88 88ooood8 88  88  88 88'  `88 88'  `88 88888"   
-- 88        88       88.  .88 88  88  88 88.  ... 88.88b.88' 88.  .88 88       88  `8b. 
-- dP        dP       `88888P8 dP  dP  dP `88888P' 8888P Y8P  `88888P' dP       dP   `YP 
                                                                                                                                                                          
Config.Framework = 'qbcore' -- qbcore/esx/exm/other

Config.MySQLScript = 'MySQL' -- MySQL / oxmysql

Config.NotificationSystem = 'qbcore' -- qbcore / esx / custom

-- Identifier used to save/load player's presets in database
Config.PlayerIdentifier = 'license' -- license / steam / discord / ip

Config.UseDatabaseHandlingSaving = true
Config.UseDatabasePresetsSaving = true

Config.UseVstancer = true

-- Owned vehicles table in database information.
-- tableName - Name of table in which all owned vehicles are stored
-- ownerColumn - Owned vehicles owner column in DB table
-- plateColumn - Vehicle plate column in DB table
if Config.Framework == 'qbcore' then
    Config.OwnedVehiclesDataTable = {
        tableName = 'player_vehicles', ownerColumn = 'citizenid', plateColumn = 'plate' -- QBCore
    }
elseif Config.Framework == 'esx' or Config.Framework == 'exm' then
    Config.OwnedVehiclesDataTable = {
        tableName = 'owned_vehicles', ownerColumn = 'owner', plateColumn = 'plate' -- ESX
    }
else
    Config.OwnedVehiclesDataTable = {}
    Config.UseDatabaseHandlingSaving = false
    Config.UseDatabasePresetsSaving = false
end

-- .d888888                                               
-- d8'    88                                               
-- 88aaaaa88a .d8888b. .d8888b. .d8888b. .d8888b. .d8888b. 
-- 88     88  88'  `"" 88'  `"" 88ooood8 Y8ooooo. Y8ooooo. 
-- 88     88  88.  ... 88.  ... 88.  ...       88       88 
-- 88     88  `88888P' `88888P' `88888P' `88888P' `88888P'                                                     
                                                        
-- Admins and their checks
Config.Admins = {
    {type = 'identifier', data = 'steam:*'},
    {type = 'frameworkgroup', data = 'god'},
    {type = 'frameworkgroup', data = 'superadmin'},
    {type = 'frameworkgroup', data = 'admin'},
    {type = 'ace', data = 'handlingtuning'},
    {type = 'ace', data = 'acommands'},
}

-- NOT recommended for RolePlay server
-- If set to true players will be able to fully adjust their vehicle
-- and make it as crazy fast as they want.
Config.AllowEveryoneFullAccess = false

-- Limited editing allows for all players to use the tablet, but only adjust vehicles handling values for limited number
-- You can adjust how much of each field can be changed by editing configs/config.js Min / Max > changeLimit
--
-- For example if vehicle's standard mass is 2000, Min.changeLimit is 200 and Max.changeLimit is 500,
-- then player will have 1800 - 2500 to play around with, as 2000 - 200 = 1800, 2000 + 500 = 2500.
--
-- THIS WILL NOT AFFECT TABLET VALUES FOR ADMINS - TABLET DEV USAGE
Config.AllowLimitedEditing = true

-- If value in config.js is not added, this will be used instead
Config.DefaultChangeLimit = 0.1

-- Name of inventory item
-- FOR QBCORE:
-- ADD TO qb-core/shared.lua  >  QBShared.Items
-- ['tunertablet'] 			 		 = {['name'] = 'tunertablet', 				['label'] = 'Tuner Tablet', 			['weight'] = 1000, 		['type'] = 'item', 		['image'] = 'tablet.png', 				['unique'] = false, 	['useable'] = true, 	['shouldClose'] = true,    ['combinable'] = nil,   ['description'] = 'Tablet used to adjust vehicle handling data'},
-- FOR ESX/EXM:
-- ADD TO database
-- INSERT INTO `items` (name, label)
-- VALUES ('tunertablet', 'Tuning Tablet');
Config.TabletItemName = 'tunertablet'

-- Command for admins full tablet access
Config.TabletCommand = 'tablet'

-- If you want any field to not be accessible by any regular player add it here
Config.BlacklistedFields = {
    'nMonetaryValue',
    'fPetrolTankVolume',
}

-- Handling groups and their parameters.
-- Params define what car modification(s) is/are needed for the value to be changable.
-- Available mods names: Engine, Suspension, Transmission, Spoiler, FrontBumper, RearBumper, Armor
Config.HandlingGroups = {
    {
        Label = 'Engine', Params = {
            Engine = 2,
            Suspension = false,
            Transmission = false,
            Spoiler = false,
            FrontBumper = false,
            RearBumper = false,
        }
    },
    {
        Label = 'Suspension', Params = {
            Engine = false,
            Suspension = 3,
            Transmission = false,
            Spoiler = false,
            FrontBumper = false,
            RearBumper = false,
            
        }
    },
    {
        Label = 'Stance', Params = {
            Engine = false,
            Suspension = 3,
            Transmission = false,
            Spoiler = false,
            FrontBumper = false,
            RearBumper = false,
            
        }
    },
    {
        Label = 'Traction', Params = {
            Engine = false,
            Suspension = 2,
            Transmission = false,
            Spoiler = false,
            FrontBumper = false,
            RearBumper = false,
            
        }
    },
    {
        Label = 'Aero', Params = {
            Engine = false,
            Suspension = false,
            Transmission = false,
            Spoiler = 1,
            FrontBumper = 1,
            RearBumper = 1,
        
        }
    },
    {
        Label = 'Damage', Params = {
            Engine = false,
            Suspension = false,
            Transmission = false,
            Spoiler = false,
            FrontBumper = false,
            RearBumper = false,
            Armor = 4,
        }
    },
    {
        Label = 'Brakes', Params = {Brakes = 4}
    },
    { -- Other is needed.
        Label = 'Other', Params = {}
    },
}

-- Scores to be displayed in diagram for the tablet
local handlingConfig = {}
Citizen.CreateThread(function()
    Citizen.Wait(100)
    if not IsDuplicityVersion() then
        handlingConfig = exports[GetCurrentResourceName()].getHandlingConfig()
        Config.ScoreValues = {
            {label = 'Aero', field = 'fDownforceModifier', max = handlingConfig.fDownforceModifier.Max.value},
            {label = 'TopSpeed', field = 'fInitialDriveMaxFlatVel', max = handlingConfig.fInitialDriveMaxFlatVel.Max.value},
            {label = 'Braking', field = 'fBrakeForce', max = handlingConfig.fBrakeForce.Max.value},
            {label = 'Acceleration', field = {'fInitialDriveForce', 'fDriveInertia'}, max = handlingConfig.fInitialDriveForce.Max.value + handlingConfig.fDriveInertia.Max.value},
        }
    end
end)

-- If you want specific field to require specific mod add it here
-- Available mods names: Engine, Suspension, Transmission, Spoiler, FrontBumper, RearBumper, Armor
Config.RequirementsForFields = {
    fSuspensionRaise = {
        Transmission = 4
    } 
}

-- Your addon vehicles models
Config.AddonVehicles = {
    'bmwe3',
    'x6m',
    'c63coupe',
    'RS72020',
    'mach1',
    'z419',
    '635',
    'rs7',
    'rs615',
    'rmodrs7',
    'e21',
    'bmwe32',
    'e34touring',
    'e34',
    'e36prb',
    'bmwe39',
    'm3kean',
    'rmodmi8lb',
    'm2',
    'm3f80',
}

Config.MaximumFetchBuffer = 3

-- 888888ba           oo .8888b   dP       8888ba.88ba                 dP          
-- 88    `8b             88   "   88       88  `8b  `8b                88          
-- 88     88 88d888b. dP 88aaa  d8888P     88   88   88 .d8888b. .d888b88 .d8888b. 
-- 88     88 88'  `88 88 88       88       88   88   88 88'  `88 88'  `88 88ooood8 
-- 88    .8P 88       88 88       88       88   88   88 88.  .88 88.  .88 88.  ... 
-- 8888888P  dP       dP dP       dP       dP   dP   dP `88888P' `88888P8 `88888P' 

Config.DriftModeEnabled = true
-- Modifications required for Drift mode to be enabled
Config.DriftGroupParams = {
    Engine = 2,
    Suspension = 3,
    Transmission = 2,
}

-- Drift mode parameters.
-- label, description - what is displayed in tablet under the Drift group configuration
-- formula - formula used to calculate changes for the field when vehicle is moving sideways
Config.DriftModeFields = {
    fInitialDriveForce = {
        label = 'Drive Force multiplier on slide',
        description = 'How much of drive force will be increased when vehicle slides',
        formula = function(defaultValue, speed, angle, multiplier) 
            local result = defaultValue + angle/15
            return result
        end},
        fSteeringLock = {
        label = 'Steering lock multiplier on slide',
        formula = function(defaultValue, speed, angle, multiplier) 
            local result = math.max(defaultValue, math.min(defaultValue  + angle, 70.0))
            return result
        end},
        fTractionCurveMin = {
        label = 'Traction decrease multiplier on slide',
        formula = function(defaultValue, speed, angle, multiplier) 
            local result = math.max(defaultValue  - angle/17, 1.0)
            return result
        end},
        fDriveBiasFront = {
        label = 'Drive bias multiplier on slide',
        formula = function(defaultValue, speed, angle, multiplier) 
            local result = math.max(defaultValue, 0.2)
            return result
        end},
}

table.has = function(t, e)
    for _,v in ipairs(t) do
        if e == v then
            return true
        end
    end
    return false
end

table.count = function(t)
    local count = 0
    for _,__ in pairs(t) do
        count = count + 1
    end
    return count
end

table.clone = function(t)
    if type(t) ~= 'table' then return t end

	local meta = getmetatable(t)
	local target = {}

	for k,v in pairs(t) do
		if type(v) == 'table' then
			target[k] = table.clone(v)
		else
			target[k] = v
		end
	end

	setmetatable(target, meta)

	return target
end
```

{% endtab %}

{% tab title="server\_config.lua" %}

```lua
ESX = nil
QBCore = nil

local DB_INFO <const> = Config.OwnedVehiclesDataTable

-- 88888888b                                                                    dP       
-- 88                                                                           88       
-- a88aaaa    88d888b. .d8888b. 88d8b.d8b. .d8888b. dP  dP  dP .d8888b. 88d888b. 88  .dP  
-- 88        88'  `88 88'  `88 88'`88'`88 88ooood8 88  88  88 88'  `88 88'  `88 88888"   
-- 88        88       88.  .88 88  88  88 88.  ... 88.88b.88' 88.  .88 88       88  `8b. 
-- dP        dP       `88888P8 dP  dP  dP `88888P' 8888P Y8P  `88888P' dP       dP   `YP 

local ServerFrameworkInit = {
    ['qbcore'] = function()
        if GetResourceState('qb-core') ~= 'started' then
            Citizen.Wait(2000)
        end
        if GetResourceState('qb-core') ~= 'started' then
            ErrorPrint('QBCore (qb-core) resource is not started.')
            ErrorPrint('Shared object can not be fetched')
            return
        end
        QBCore = exports['qb-core']:GetCoreObject()
    end,
    ['esx'] = function()
        TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
        if ESX then return end
        if GetResourceState('es_extended') ~= 'started' then
            ErrorPrint('ESX (es_extended) resource is not started.')
            ErrorPrint('Shared object can not be fetched.')        
        end
        if ESX == nil then
            ErrorPrint('ESX shared object was not fetched.')
        end
    end,
    ['exm'] = function()
        ESX = exports.extendedmode:getSharedObject()
    end,
    ['other'] = function()
        -- Add your code here
    end,
    ['none'] = function() end,
}

Notify = {
    ['qbcore'] = function(src, text, type, length)
        TriggerClientEvent('QBCore:Notify', src, text, type, length)
    end,
    ['esx'] = function(src, ...)
        TriggerClientEvent('esx:showNotification', src, ...)
    end,
    ['custom'] = function(src, msg, flash, saveToBrief, hudColorIndex)
        TriggerClientEvent('handlingtuning:Notify', src, msg, flash, saveToBrief, hudColorIndex)
    end,
}

Citizen.CreateThread(function()
    if ServerFrameworkInit[Config.Framework] == nil then
        ErrorPrint(('Framework initiation for option ^6 %s ^7 does not exist'):format(Config.Framework))
        return
    end
    ServerFrameworkInit[Config.Framework]()
end)

RegisterNetEvent('sb-interaction:errorPrint', function(msg)
    print(msg)
end)

function ErrorPrint(...)
    local text = table.concat({...}, ' ')
    local string = ('[^6ERROR^7] - %s ^7'):format(text)
    print(string)
end

function getIsAdmin(src)
    if Config.AllowEveryoneFullAccess then return true end
    local playerGroup = ''
    local identifiers = GetPlayerIdentifiers(src)
    for i,v in ipairs(Config.Admins) do
        if v.type == 'identifier' then
            for _,identifier in pairs(identifiers) do
                if identifier == v.data then return true end
            end
        elseif v.type == 'frameworkgroup' then
            if Config.Framework == 'qbcore' then
                if QBCore and QBCore.Functions.HasPermission(src, v.data) then
                    return true
                end
            elseif table.has({'esx', 'exm'}, Config.Framework) then
                local xPlayer = ESX.GetPlayerFromId(src)
                if xPlayer.getGroup() == v.data then
                    return true
                end
            else
                ErrorPrint('Permission check code for your framework was not set up.')
                -- Add code for your framework permission checks
            end
        elseif v.type == 'ace' then
            if IsAceAllowed(v.data) then
                return true
            end
        end
    end

    return false
end

function getPlayerIdentifier(src)
    for _, identifier in pairs(GetPlayerIdentifiers(src)) do
        if string.find(identifier, Config.PlayerIdentifier) then
            return identifier
        end
    end
    return nil
end

function getCharacterIdentifier(src)
    if table.has({'esx', 'exm'}, Config.Framework) then
        local xPlayer = ESX.GetPlayerFromId(src)
        return xPlayer and xPlayer.getIdentifier() or nil
    elseif Config.Framework == 'qbcore' then
        local ply = QBCore.Functions.GetPlayer(src)
        return ply and ply.PlayerData.citizenid or nil
    else
        return getPlayerIdentifier(src)
    end
end

function registerItem()
    if Config.Framework == 'qbcore' then
        QBCore.Functions.CreateUseableItem("tunertablet", function(source, item)
            local Player = QBCore.Functions.GetPlayer(source)
            if Player.Functions.GetItemByName(item.name) ~= nil then
                TriggerClientEvent("handlingtuning:client:openTablet", source, false)
            end
        end)
    elseif table.has({'esx', 'exm'}, Config.Framework) then
        ESX.RegisterUsableItem(Config.TabletItemName, function(source)
            TriggerClientEvent("handlingtuning:client:openTablet", source, false)
        end)
    end
end

-- 8888ba.88ba           .d88888b   .88888.   dP        
-- 88  `8b  `8b          88.    "' d8'   `8b  88        
-- 88   88   88 dP    dP `Y88888b. 88     88  88        
-- 88   88   88 88    88       `8b 88  db 88  88        
-- 88   88   88 88.  .88 d8'   .8P Y8.  Y88P  88        
-- dP   dP   dP `8888P88  Y88888P   `8888PY8b 88888888P 
--                   .88                                
--               d8888P                                 

function isVehicleOwned(plate)
    local retval
    if Config.UseDatabaseHandlingSaving then
        local res = mysqlQuerySync(('SELECT 1 FROM %s WHERE %s LIKE \'%s\''):format(DB_INFO.tableName, DB_INFO.plateColumn, '%'..plate..'%'), {})
        if res then
            retval = res[1]
        end
    else
        retval = false
    end
    return retval
end

function setNewHandling(plate, handlingData)
    if Config.UseDatabaseHandlingSaving then
        mysqlQuery(('UPDATE %s SET handling = @handlingData WHERE %s LIKE \'%s\''):format(DB_INFO.tableName, DB_INFO.plateColumn, '%'..plate..'%'),
        {['handlingData'] = json.encode(handlingData)})
    end
end

function mysqlQuery(query, params, cb)
    if Config.MySQLScript == 'oxmysql' then
        MySQL.Async.fetchAll(query, params, cb)
    elseif Config.MySQLScript == 'MySQL' then
        MySQL.Async.fetchAll(query, params, cb)
    end
end

function mysqlQuerySync(query, params) 
    if Config.MySQLScript == 'oxmysql' then
        return MySQL.Sync.fetchAll(query, params)
    elseif Config.MySQLScript == 'MySQL' then
        return MySQL.Sync.fetchAll(query, params)
    end

end

RegisterNetEvent('handlingtuning:server:saveHandling', function(handlingName, handlingData, vehName)
    local src = source
    if Config.UseDatabasePresetsSaving then
        mysqlQuery('INSERT INTO player_handlings (identifier, handlingData, carName, handlingName) VALUES (@id, @handlingData, @carName, @handlingName)', {
            ['id'] = getPlayerIdentifier(src),
            ['handlingData'] = json.encode(handlingData),
            ['carName'] = vehName,
            ['handlingName'] = handlingName
        })
    end
end)

RegisterNetEvent('handlingtuning:server:delHandling', function(presetId)
    local src = source
    mysqlQuery('DELETE FROM player_handlings WHERE `id` = @id AND `identifier` = @identifier', {id = presetId, identifier = getPlayerIdentifier(src)})
end)

RegisterNetEvent('handlingtuning:server:sharePreset', function(targetId, preset)
    if Config.UseDatabasePresetsSaving then
        local src = source
        if getPlayerIdentifier(targetId) then
            mysqlQuery('INSERT INTO player_handlings (identifier, handlingData, carName, handlingName, creator) VALUES (@id, @handlingData, @carName, @handlingName, @plyName)', {
                ['id'] = getPlayerIdentifier(tonumber(targetId)),
                ['handlingData'] = json.encode(preset.handlingData),
                ['carName'] = preset.carName,
                ['handlingName'] = preset.handlingName,
                ['plyName'] = GetPlayerName(src),
            })
            Notify[Config.NotificationSystem](tonumber(targetId), "Someone shared a new tuning tablet handling preset with you.")
        else
            Notify[Config.NotificationSystem](src, "Player is not online.")
        end
    end
end)

-- a88888b.                                                        dP          
-- d8'   `88                                                        88          
-- 88        .d8888b. 88d8b.d8b. 88d8b.d8b. .d8888b. 88d888b. .d888b88 .d8888b. 
-- 88        88'  `88 88'`88'`88 88'`88'`88 88'  `88 88'  `88 88'  `88 Y8ooooo. 
-- Y8.   .88 88.  .88 88  88  88 88  88  88 88.  .88 88    88 88.  .88       88 
--  Y88888P' `88888P' dP  dP  dP dP  dP  dP `88888P8 dP    dP `88888P8 `88888P'                                                                        

RegisterCommand(Config.TabletCommand, function(s, a)
    if getIsAdmin(s) then
        TriggerClientEvent('handlingtuning:client:openTablet', s, getIsAdmin(s))
    end
end)

-- -- Can be used to open minimal menu straight away
-- RegisterCommand('minimal', function(s, a)
--     if getIsAdmin(s) then
--         TriggerClientEvent('handlingtuning:client:openMinimal', s, getIsAdmin(s))
--     end
-- end)

```

{% endtab %}

{% tab title="client\_config.lua" %}

```lua
ESX = nil
QBCore = nil

local ClientFrameworkInit = {
    ['qbcore'] = function()
        if GetResourceState('qb-core') ~= 'started' then
            Citizen.Wait(2000)
        end
        if GetResourceState('qb-core') ~= 'started' then
            ErrorPrint('QBCore (qb-core) resource is not started on client side.')
            ErrorPrint('Shared object can not be fetched')
            return
        end
        QBCore = exports['qb-core']:GetCoreObject()
    end,
    ['esx'] = function()
        local startTime = GetGameTimer()
        while ESX == nil do
            TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
            Citizen.Wait(0)
            if GetGameTimer() - 5000 > startTime then
                ErrorPrint('ESX not fetched on Client side.')
                ErrorPrint('Make sure your shared even name is set correctly in ^3configs/client_config.lua^7 and you set the correct framework')
                return
            end
        end
        if ESX then return end
        if GetResourceState('es_extended') ~= 'started' then
            ErrorPrint('ESX (es_extended) resource is not started on client side.')
            ErrorPrint('Shared object can not be fetched')        
        end
    end,
    ['exm'] = function()
        ESX = exports.extendedmode:getSharedObject()
    end,
    ['other'] = function()
        -- Add your code here
    end,
    ['none'] = function() end,
}

Notify = {
    ['qbcore'] = function(text, type, length)
        QBCore.Functions.Notify(text, type, length)
    end,
    ['esx'] = function(...)
        ESX.ShowNotification(...)
    end,
    ['custom'] = function(msg, flash, saveToBrief, hudColorIndex)
        if saveToBrief == nil then saveToBrief = true end
        AddTextEntry('handlingNotify', msg)
        BeginTextCommandThefeedPost('handlingNotify')
        if hudColorIndex then ThefeedNextPostBackgroundColor(hudColorIndex) end
        EndTextCommandThefeedPostTicker(flash or false, saveToBrief)
    end,
}

RegisterNetEvent('handlingtuning:Notify', function(...)
    Notify[Config.NotificationSystem](...)
end)

Citizen.CreateThread(function()
    if ClientFrameworkInit[Config.Framework] == nil then
        ErrorPrint(('Framework initiation for option ^6 %s ^7 does not exist'):format(Config.Framework))
        return
    end
    ClientFrameworkInit[Config.Framework]()
end)

function ErrorPrint(...)
    local text = table.concat({...}, ' ')
    local string = ('[^6ERROR^7] - %s ^7'):format(text)
    print(string)
    if not CFG or CFG.SendClientSideErrorsToServer then return end
    TriggerServerEvent('sb-interaction:errorPrint', ('[^4Client^7] %s ^7'):format(string))
end
```

{% endtab %}

{% tab title="config.js" %}

```javascript
var CHandlingData = {
    fMass: {
        Label: 'Mass',
        Group: 'Body',
        Min: {
            value: '100',
            changeLimit: '200',
        },
        Max: {
            value: '60000',
            changeLimit: '500',
        },
        Description:
            'This is the weight of the vehicle in kilograms. Only used when the vehicle collides with another vehicle or non-static object.',
    },
    fInitialDragCoeff: {
        Label: 'Drag coefficient',
        Group: 'Aero',
        Min: {
            value: '0.9',
            changeLimit: '0.6',
        },
        Max: {
            value: '300',
            changeLimit: '2.5',
        },
        Description:
            'Sets the drag coefficient on the rage physics archetype of the vehicle (proportional to velocity squared). Increase to simulate aerodynamic drag.',
    },
    fPercentSubmerged: {
        Label: 'Water floating percentage',
        Group: 'Other',
        Min: {
            value: '45',
            changeLimit: '20',
        },
        Max: {
            value: '200',
            changeLimit: '20',
        },
        Description:
            "A percentage of vehicle height in the water before vehicle 'floats'. So as the vehicle falls into the water, at 85% vehicle height (seemingly the default for road vehicles) it will stop sinking to float for a moment before it sinks (boats excluded).",
    },
    fDriveBiasFront: {
        Label: 'Drive front bias',
        Group: 'Engine',
        Min: {
            value: '0',
            changeLimit: '20',
        },
        Max: {
            value: '1',
            changeLimit: '20',
        },
        Description: 'This value determines how much power is passed to front wheels, if value is 0, car will be rear wheels drive, 100 - front wheel drive, anything in between will make vehicle four wheels drive.',
    },
    nInitialDriveGears: {
        Label: 'Amount of gears',
        Group: 'Engine',
        Min: {
            value: '1',
            changeLimit: '2',
        },
        Max: {
            value: '10',
            changeLimit: '2',
        },
        Description: 'How many forward speeds a transmission contains.',
    },
    fInitialDriveForce: {
        Label: 'Drive force',
        Group: 'Engine',
        Min: {
            value: '0',
            changeLimit: '0.6',
        },
        Max: {
            value: '5',
            changeLimit: '0.2',
        },
        Description:
        "This multiplier modifies the game's calculation of drive force (from the output of the transmission).",
    },
    fDriveInertia: {
        Label: 'Drive inertia',
        Group: 'Engine',
        Min: {
            value: '0.1',
            changeLimit: '0.6',
        },
        Max: {
            value: '2.5',
            changeLimit: '0.2',
        },
        Description:
        'Describes how fast an engine will rev. For example an engine with a long stroke crank and heavy flywheel will take longer to redline than an engine with a short stroke and light flywheel.',
    },
    fClutchChangeRateScaleUpShift: {
        Label: 'Shift UP speed',
        Group: 'Engine',
        Min: {
            value: '0.3',
            changeLimit: '2.0',
        },
        Max: {
            value: '9',
            changeLimit: '1.0',
        },
        Description: 'Clutch speed multiplier on up shifts, bigger number = faster shifts.',
    },
    fClutchChangeRateScaleDownShift: {
        Label: 'Shift DOWN speed',
        Group: 'Engine',
        Min: {
            value: '0.3',
            changeLimit: '2.0',
        },
        Max: {
            value: '9',
            changeLimit: '1.0',
        },
        Description: 'Clutch speed multiplier on down shifts, bigger number = faster shifts.',
    },
    fInitialDriveMaxFlatVel: {
        Label: 'Max Speed',
        Group: 'Engine',
        Min: {
            value: '10',
            changeLimit: '100.0',
        },
        Max: {
            value: '328.6',
            changeLimit: '30.0',
        },
        Description:
            'Determines the speed at redline in top gear. Setting this value does not guarantee the vehicle will reach this speed. Multiply the number in the file by 0-82 to get the speed in mph or multiply by 1.32 to get kph.',
    },
    fBrakeForce: {
        Label: 'Braking force',
        Group: 'Brakes',
        Min: {
            value: '0.001',
            changeLimit: '1.0',
        },
        Max: {
            value: '5',
            changeLimit: '0.75',
        },
        Description: "Multiplies the game's calculation of deceleration. Bigger number = harder braking.",
    },
    fBrakeBiasFront: {
        Label: 'Brake front bias',
        Group: 'Brakes',
        Min: {
            value: '0',
            changeLimit: '0.5',
        },
        Max: {
            value: '0.8',
            changeLimit: '0.5',
        },
        Description: 'This controls the distribution of braking force between the front and rear axles.',
    },
    fHandBrakeForce: {
        Label: 'Hand Brake force',
        Group: 'Brakes',
        Min: {
            value: '0.01',
            changeLimit: '2.0',
        },
        Max: {
            value: '6',
            changeLimit: '1.5',
        },
        Description: 'Braking power for handbrake. Bigger number = harder braking.',
    },
    fSteeringLock: {
        Label: 'Steering lock',
        Group: 'Suspension',
        Min: {
            value: '20',
            changeLimit: '30.0',
        },
        Max: {
            value: '90',
            changeLimit: '30.0',
        },
        Description:
        "This value is a multiplier of the game's calculation of the angle a steer wheel will turn while at full turn. Steering lock is directly related to over or understeer / turning radius.",
    },
    fTractionCurveMax: {
        Label: 'Traction curve max',
        Group: 'Traction',
        Min: {
            value: '0',
            changeLimit: '1.3',
        },
        Max: {
            value: '3.7',
            changeLimit: '0.75',
        },
        Description: 'Cornering grip of the vehicle as a multiplier of the tire surface friction.',
    },
    fTractionCurveMin: {
        Label: 'Traction curve min',
        Group: 'Traction',
        Min: {
            value: '0',
            changeLimit: '1.3',
        },
        Max: {
            value: '3.5',
            changeLimit: '0.75',
        },
        Description: 'Accelerating/braking grip of the vehicle as a multiplier of the tire surface friction.',
    },
    fTractionCurveLateral: {
        Label: 'Traction curve lateral',
        Group: 'Traction',
        Min: {
            value: '1',
            changeLimit: '0.5',
        },
        Max: {
            value: '120',
            changeLimit: '0.5',
        },
        Description: 'Shape of lateral traction curve (peak traction position in degrees).',
    },
    fTractionSpringDeltaMax: {
        Label: 'Traction spring delta max',
        Group: 'Traction',
        Min: {
            value: '0.02',
            changeLimit: '0.5',
        },
        Max: {
            value: '0.5',
            changeLimit: '0.5',
        },
        Description: 'This value denotes at what distance above the ground the car will lose traction.',
    },
    fLowSpeedTractionLossMult: {
        Label: 'Traction loss multiplier',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '0.5',
        },
        Max: {
            value: '2.2',
            changeLimit: '0.5',
        },
        Description:
        "How much traction is reduced at low speed, 0.0 means normal traction. It affects mainly car burnout (spinning wheels when car doesn't move) when pressing gas. Decreasing value will cause less burnout, less sliding at start. However, the higher value, the more burnout car gets. Optimal is 1.0.",
    },
    fCamberStiffnesss: {
        Label: 'Camber stiffness',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '0.3',
        },
        Max: {
            value: '1.12',
            changeLimit: '0.3',
        },
        Description:
        "This value modify the grip of the car when you're drifting and you release the gas. In general, it makes your car slide more on sideways movement. More than 0 make the car sliding on the same angle you're drifting and less than 0 make your car oversteer (not recommend to use more than 0.1 / -0.1 if you don't know what you're doing).",
    },
    fTractionBiasFront: {
        Label: 'Traction front bias',
        Group: 'Traction',
        Min: {
            value: '0.325',
            changeLimit: '0.3',
        },
        Max: {
            value: '0.95',
            changeLimit: '0.3',
        },
        Description: 'Determines the distribution of traction from front to rear.',
    },
    fTractionLossMult: {
        Label: 'Traction loss multiplier',
        Group: 'Traction',
        Min: {
            value: '0',
            changeLimit: '0.3',
        },
        Max: {
            value: '1.4',
            changeLimit: '0.3',
        },
        Description:
        'How much is traction affected by material grip differences from 1.0. Basically it affects how much grip is changed when driving on asphalt and mud (the higher, the more grip you loose, making car less responsive and prone to sliding).',
    },
    fSuspensionForce: {
        Label: 'Suspension stiffness',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '2.0',
        },
        Max: {
            value: '9',
            changeLimit: '2.0',
        },
        Description:
        '1 / (Force * NumWheels) = Lower limit for zero force at full extension. Affects how strong suspension is. Can help if car is easily flipped over when turning.',
    },
    fSuspensionCompDamp: {
        Label: 'Suspenssion damper comp stiffness',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '2.0',
        },
        Max: {
            value: '8',
            changeLimit: '2.0',
        },
        Description: 'Damping during strut compression. Bigger = stiffer.',
    },
    fSuspensionReboundDamp: {
        Label: 'Suspension rebound damp stiffness',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '2.0',
        },
        Max: {
            value: '10.8',
            changeLimit: '2.0',
        },
        Description: 'Damping during strut rebound. Bigger = stiffer.',
    },
    fSuspensionUpperLimit: {
        Label: 'Suspension upper limit',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '0.3',
        },
        Max: {
            value: '0.8',
            changeLimit: '0.3',
        },
        Description: 'Visual limit... how far can wheels move up / down from original position.',
    },
    fSuspensionLowerLimit: {
        Label: 'Suspension lower limit',
        Group: 'Suspension',
        Min: {
            value: '-0.36',
            changeLimit: '0.3',
        },
        Max: {
            value: '0.1',
            changeLimit: '0.3',
        },
        Description: 'Visual limit... how far can wheels move up / down from original position.',
    },
    fSuspensionRaise: {
        Label: 'Suspension height',
        Group: 'Suspension',
        Min: {
            value: '-0.085',
            changeLimit: '0.3',
        },
        Max: {
            value: '0.35',
            changeLimit: '0.3',
        },
        Description:
        'The amount that the suspension raises the body off the wheels. Recommend adjusting at second decimal unless vehicle has room to move. ie -0.02 is plenty of drop on an already low car. Too much will show the wheels clipping through or if positive, no suspension joining the body to wheels.',
    },
    fSuspensionBiasFront: {
        Label: 'Suspension front bias',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '0.3',
        },
        Max: {
            value: '0.85',
            changeLimit: '0.3',
        },
        Description:
        'Force damping scale front/back. If more wheels at back (e.g. trucks) need front suspension to be stronger. This value determines which suspension is stronger, front or rear. If value is above 0.50 then front is stiffer, when below, rear.',
    },
    fAntiRollBarForce: {
        Label: 'Antiroll bar stiffness',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '0.5',
        },
        Max: {
            value: '3',
            changeLimit: '0.5',
        },
        Description:
        'The spring constant that is transmitted to the opposite wheel when under compression larger numbers are a larger force. Larger Numbers = less body roll.',
    },
    fAntiRollBarBiasFront: {
        Label: 'Front Antiroll bar stiffness',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '0.5',
        },
        Max: {
            value: '1',
            changeLimit: '0.5',
        },
        Description: 'The bias between front and rear for the antiroll bar(0 front, 1 rear).',
    },
    fRollCentreHeightFront: {
        Label: 'Roll centre height front',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '0.5',
        },
        Max: {
            value: '1.2',
            changeLimit: '0.5',
        },
        Description: 'This value modify the weight transmission during an acceleration between the left and right.',
    },
    fRollCentreHeightRear: {
        Label: 'Roll centre height rear',
        Group: 'Suspension',
        Min: {
            value: '0',
            changeLimit: '0.5',
        },
        Max: {
            value: '1.2',
            changeLimit: '0.5',
        },
        Description:
        'This value modify the weight transmission during an acceleration between the front and rear (and can affect the acceleration speed).',
    },
    fCollisionDamageMult: {
        Label: 'Crash damage multiplier',
        Group: 'Damage',
        Min: {
            value: '0.005',
            changeLimit: '0.5',
        },
        Max: {
            value: '2',
            changeLimit: '0.5',
        },
        Description: "Multiplies the game's calculation of damage to the vehicle through collision.",
    },
    fWeaponDamageMult: {
        Label: 'Weapon damage multiplier',
        Group: 'Damage',
        Min: {
            value: '0.03',
            changeLimit: '0.5',
        },
        Max: {
            value: '4',
            changeLimit: '0.5',
        },
        Description: "Multiplies the game's calculation of damage to the vehicle through weapon damage.",
    },
    fDeformationDamageMult: {
        Label: 'Deformation multiplier',
        Group: 'Damage',
        Min: {
            value: '0',
            changeLimit: '0.5',
        },
        Max: {
            value: '5',
            changeLimit: '0.5',
        },
        Description: "Multiplies the game's calculation of deformation-causing damage.",
    },
    fEngineDamageMult: {
        Label: 'Engine damage multiplier',
        Group: 'Damage',
        Min: {
            value: '0.01',
            changeLimit: '0.5',
        },
        Max: {
            value: '2.5',
            changeLimit: '0.5',
        },
        Description: "Multiplies the game's calculation of damage to the engine, causing explosion or engine failure.",
    },
    fPetrolTankVolume: {
        Label: 'Fuel tank volume',
        Min: {
            value: '0',
            changeLimit: '20.5',
        },
        Max: {
            value: '5000',
            changeLimit: '20.5',
        },
        Description: "Amount of petrol that will leak after shooting the vehicle's petrol tank.",
    },
    fOilVolume: {
        Label: 'Oil volume',
        Min: {
            value: '0',
            changeLimit: '2.0',
        },
        Max: {
            value: '10',
            changeLimit: '2.0',
        },
        Description: 'Black smoke time before engine dies?',
    },
    fSeatOffsetDistX: {
        Label: 'Seat offset X',
        Editable: 'false',
        Min: {
            value: '-0.2',
            changeLimit: '0.0',
        },
        Max: {
            value: '0.3',
            changeLimit: '0.0',
        },
        Description: 'The distance from the door to the car seat.',
    },
    fSeatOffsetDistY: {
        Label: 'Seat offset Y',
        Editable: 'false',
        Min: {
            value: '-0.6',
            changeLimit: '0.0',
        },
        Max: {
            value: '0.3',
            changeLimit: '0.0',
        },
        Description: 'The distance from the door to the car seat.',
    },
    fSeatOffsetDistZ: {
        Label: 'Seat offset Z',
        Min: {
            value: '-0.4',
            changeLimit: '0.0',
        },
        Max: {
            value: '0.5',
            changeLimit: '0.0',
        },
        Description: 'The distance from the door to the car seat.',
    },
    nMonetaryValue: {
        Label: 'Vehicle value',
        Min: {
            value: '10000',
            changeLimit: '0.0',
        },
        Max: {
            value: '500000',
            changeLimit: '0.0',
        },
        Description: 'Vehicle worth.',
    },
    // strModelFlags: {
    //     Label: 'Model Flags',
    //     Editable: 'false',
    //     Description: 'Model flags. Written in HEX. Rightmost digit is the first one.',
    //     Min:{
    //         value: '0',
    //     },
    //     Max:{
    //         value: '999999',
    //     }
    // },
    // strHandlingFlags: {
    //     Label: 'Handling Flags',
    //     Editable: 'false',
    //     Description: 'Handling flags. Written in HEX. Rightmost digit is the first one.',
    //     Min:{
    //         value: '0',
    //     },
    //     Max:{
    //         value: '999999',
    //     }
    // },
    // strDamageFlags: {
    //     Label: 'Damage Flags',
    //     Editable: 'false',
    //     Description: 'Indicates the doors that are nonbreakable. Written in HEX. Rightmost digit is the first one.',
    //     Min:{
    //         value: '0',
    //     },
    //     Max:{
    //         value: '999999',
    //     }
    // },
    vecInertiaMultiplier: {
        Label: 'Inertial multiplier',
        Min:{
            value: '-5',
            changeLimit: '0.5',
        },
        Max:{
            value: '10',
            changeLimit: '0.5',
        }
    },
    vecCentreOfMassOffset: {
        Label: 'Centre of mass offset',
        Description: 'Defines center of mass offset. If car easily flips on turns you most likely want to change this an set offset lower.',
        Min:{
            value: '-5',
            changeLimit: '0.5',
        },
        Max:{
            value: '10',
            changeLimit: '0.5',
        }
    },
    // AIHandling: {
    //     Editable: 'false',
    //     Description:
    //         'Tells the AI which driving profile it should use when driving the vehicle. Use AVERAGE for boats, bikes, aircraft, etc.',
    // },
    // SubHandlingData: {
    //     Editable: 'false',
    //     Description: {
    //         selfClosing: 'true',
    //     },
    // },
    fWeaponDamageScaledToVehHealthMult: {
        Label: 'Weapon damage to veh health multiplier',
        Group: 'Damage',
        Min: {
            value: '0.0',
            changeLimit: '0.2',
        },
        Max: {
            value: '0.5',
            changeLimit: '0.2',
        },
    },
    fPopUpLightRotation: {
        Label: 'Pop up light rotation',
        Min: {
            value: '17.5',
            changeLimit: '0.2',
        },
        Max: {
            value: '45',
            changeLimit: '0.2',
        },
        Description: {
        },
    },
    fDownforceModifier: {
        Label: 'Downforce multiplier',
        Group: 'Aero',
        Min: {
            value: '1.15',
            changeLimit: '5.5',
        },
        Max: {
            value: '300',
            changeLimit: '1.5',
        },
    },
    fRocketBoostCapacity: {
        Label: 'Rocket Boost capacity',
        Group: 'Engine',
        Min: {
            value: '3',
            changeLimit: '1.5',
        },
        Max: {
            value: '20',
            changeLimit: '1.5',
        },
    },
    fBoostMaxSpeed: {
        Label: 'Boost max speed',
        Group: 'Engine',
        Min: {
            value: '15',
            changeLimit: '1.5',
        },
        Max: {
            value: '87.5',
            changeLimit: '1.5',
        },
    },
};

exports('getHandlingConfig', () => {
    return CHandlingData
})
```

{% endtab %}
{% endtabs %}

## How to use

### Option 1 - Full access

You can use the handling tuning system with full access using a chat command `/tablet`. For players to have access to the command you need to give access using any of `Config.Admins` permissions. This will allow players to access all the features of the system including:

* All handling parameters to be adjusted without any limit (in the range set in `config.js`);
* Change parameters in all groups on any vehicle, without needing to upgrade the vehicle first;
* Access Drift mode on any vehicle;
* Ability to generate and copy XML file of vehicle's handling;
* Access to either *Minimal* or *Tablet* UIs;
* Adjust owned vehicles and have their values saved to database.

### Option 2 - Limited access

Re**commended for RP servers**. You can allow players to use an inventory item to access the *Tablet* UI and adjust their vehicles in a limited range, which depends on original vehicle handling. This method will allow players to access their, or others vehicles to their liking and will include:

* A limited parameters range (limits can be set in `config.js`);
* Access to Drift mode if vehicle meets upgrades requirements;
* Access to all handling groups if vehicle meets upgrades requirements;
* Access to either *Minimal* or *Tablet* UIs;
* Adjust owned vehicles and have their values saved to database.

{% hint style="info" %}
If you have admin permissions on the script and open the tablet using item you will still have a limited access to it, if you want the full access you must use `/tablet`.
{% endhint %}

## API

API exports functions and events (client side):

#### openTablet(fullAccess) / openMinimal(fullAccess)

Opens tablet or minimal UI.

Paremeters:

* `fullAccess` - boolean(true/false)\
  Full access will allow player to generate XML code of the handling, adjust the values without any limits. See more in [https://github.com/daZepelin/sb-handlingtuning/blob/gitbook/docs/handling-tuning.md#how-to-use](https://github.com/daZepelin/sb-handlingtuning/blob/gitbook/docs/handling-tuning.md#how-to-use "mention")

{% code title="client.lua" %}

```lua
-- TABLET UI
-- Full Access
exports['sb-handlingtuning']:openTablet(true)

-- Limited Access
exports['sb-handlingtuning']:openTablet(false)

-- MINIMAL UI
-- Full Access
exports['sb-handlingtuning']:openMinimal(true)

-- Limited Access
exports['sb-handlingtuning']:openMinimal(false)
```

{% endcode %}

#### setDriftMode(toggle)

Sets Drift Mode either enabled or disabled.

Parameters:

* `toggle` - boolean(true/false)\
  Enable disable the drift mode of a vehicle player is currently sitting in.

{% code title="client.lua" %}

```lua
-- Enable
exports['sb-handlingtuning']:setDriftMode(true)

-- Disable
exports['sb-handlingtuning']:setDriftMode(false)
```

{% endcode %}

#### setVehicleHandlingValue(key, value, vehicle)

Use instead of natives for vehicle handling (*SetVehicleHandlingField, SetVehicleHandlingFloat, SetVehicleHandlingInt, SetVehicleHandlingVector*). If you have any script that uses these natives replace them with this export.

Parameters:

* `key` - string\
  vehicle handling field (e.g `nInitialDriveGears`, `fInitialDriveForce`);
* `value` - number\
  value for field to set;
* `vehicle` - vehicle handler (optional, default vehicle player is in).

Example to replace in other script:

{% code title="client.lua" %}

```lua
--SetVehicleHandlingFloat(currentVehicle, "CHandlingData", "fClutchChangeRateScaleDownShift", 0.0)
exports['sb-handlingtuning']:setVehicleHandlingValue('fClutchChangeRateScaleDownShift', 0.0, currentVehicle)
```

{% endcode %}

#### resetHandling()

Resets handling to standard vehicle values.

{% code title="client.lua" %}

```lua
exports['sb-handlingtuning']:resetHandling()
```

{% endcode %}

**SetFrontCamber(vehicle, value)** / **SetRearCamber(vehicle, value)**

Sets vehicle front / rear camber for stance.

{% code title="client.lua" %}

```lua
exports['sb-handlingtuning']:SetFrontCamber() -- Sets front Camber
exports['sb-handlingtuning']:SetRearCamber() -- Sets rear Camber
```

{% endcode %}

**SetFrontWheelOffset(vehicle, value)** / **SetRearWheelsOffset(vehicle, value)**

Sets vehicle front / rear wheels offset for stance.

{% code title="client.lua" %}

```lua
exports['sb-handlingtuning']:SetFrontWheelOffset() -- Sets fron wheels ofsset
exports['sb-handlingtuning']:SetRearWheelsOffset() -- Sets rear wheels offset
```

{% endcode %}

## Common Issues / FAQ

### Why I can not see all options?

You probably are using tablet with limited access. In `Config.Groups` you can set that for example `Engine` group would require engine level 4 upgrade installed on the vehicle. So you either want to install all the upgrades or open tablet using `/tablet` command. See more information in [https://github.com/daZepelin/sb-handlingtuning/blob/gitbook/docs/handling-tuning.md#how-to-use](https://github.com/daZepelin/sb-handlingtuning/blob/gitbook/docs/handling-tuning.md#how-to-use "mention")

### Why I can not toggle Drift Mode?

See [https://github.com/daZepelin/sb-handlingtuning/blob/gitbook/docs/handling-tuning.md#why-i-can-not-see-all-options](https://github.com/daZepelin/sb-handlingtuning/blob/gitbook/docs/handling-tuning.md#why-i-can-not-see-all-options "mention").

### I do not use any framework, will handling system work on my server?

Yes, the system has build in options to work without any framework and any other scripts. To make it work you would need to set `Config.Framework` and `Config.NotificationSystem` to `'custom'`. The framework implementation only allows owned vehicles to save their handlings.

### I change handling values, but they make no effect, when I reload tablet they get back to default

This most likely happens because you have some other script that changes handling values and interrupts sb-handlingtuning functionality. Make sure you do not have any other *handling editors*, *tuning tablets* or *engine swap* scripts installed in your server.\
\
Mostly often it is `qb-vehiclefailure` or `fivem-realisticvehicle` resources that affect braking force.

## Changelog

#### v1.0.0.3

* Fixed some of database issues and loading on esx and mysql-async scritpts;
* Fixed pressing E and Q fading in the minimal UI even if the tablet UI is on;
* Fixed some UI errors.

#### v1.0.1 - v1.0.1.2

* Fixes on key presses causing UI errors;
* Created and added more errors handlers for easier debugging;
* Fixed some sql queries to work with older mysql-async versions;
* Fixed issue that caused break on saving presets and generating xml files;
* Adjusted the way standard vehicles are being loaded. Now it might take longer, but will more likely not break and get stuck.

#### v1.0.1.3

* Added API export functions. How to use them you can find in [https://github.com/daZepelin/sb-handlingtuning/blob/gitbook/docs/handling-tuning.md#api](https://github.com/daZepelin/sb-handlingtuning/blob/gitbook/docs/handling-tuning.md#api "mention");
* Fixed some issues with server callbacks.

#### v1.0.2

* Changed json to longtext in sql queries, as for some db versions it was causing issues;
* Fixed issue where drift mode values were resetting when not intended;
* Added missing handling fields, that had their groups missing;
* Drift group values were saving some not needed values to standard values;
* Added tablet command name config;
* Added api for setting handling values, now the system will be easier to implement to other of your systems.

**v1.0.3**

* Added wheels stancer tuning to the tablet. You can adjust wheels base width and camber. For now this is only synced with players inside the car. Also created api for that;
* Added *drifting heat*. If drift mode is on this will reduce the tires grip after drifting and will gradually get back to normal grip if not drifting anymore. This should help initiate next slide after being in drift;
* Fixed tablet icons and property description element;
* Fixed some issues with drift mode, which was not activating in some cases.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://squirrel-bracket.gitbook.io/squirrel-bracket-documentation/handling-tuning-v1/readme.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
