ESX

Quasar Inventory for ESX Installation Guide, please don't forget to read this installation step by step.

1. DOWNLOAD

The first step is to download the resource that we just bought in our https://keymaster.fivem.net/asset-grants, it is important that you log in to the account with which you bought said resource, otherwise it will never appear.

2. ASSET POSITION

When downloading the resource, we will see a .zip file with the complete package of the resource inside.It is important not to rename resources as doing so will stop them from working completely, or even cause critical errors.
  • In the [img] folder you will have all the default images of the items.
  • In the [sql] folder you will have the files for SQL and objects for both QBCore and ESX.
  • The rest are the script folders, remember not to edit or delete the .fxa files.
Inside your server folder, we will create a folder called [smartphone] and add all the downloaded package inside, if you want to use the DLC you can also add a folder as shown in the image to add the DLC inside in an organized way.
Example of resources within [inventory].

3. DATABASE

Inside the package we will find a folder called [sql] which we will install inside our database.
This is the most important step, if it is not executed correctly, you will never be able to access the Inventory.
We have two options, one is to simply remove the characters from the server and everything will work fine.
But the most viable option is to put this event inside our database, don't forget to modify the YOUR_DB_NAME.
UPDATE YOUR_DB_NAME.users SET inventory = '[]' WHERE identifier IS NOT NULL

4. DEPENDENCIES

Quasar Inventory has a single dependency to work with, so we will download the progressbar by clicking on the link.
If you don't download and run this dependency, the resource will not start.

5. SERVER.CFG

It is important to maintain an order in server.cfg , as this system depends on your server.cfg starts and order.
All the resources that you start, must be under es_extended or the ESX cores, you must also start them one by one, never in a single folder.
ensure qs-core
ensure qs-inventory
ensure qs-shops
ensure progressbar
In case of also starting the DLC, we must start them below all the resources that we mentioned above.
ensure qs-core
ensure qs-inventory
ensure qs-shops
ensure progressbar
ensure qs-backpack
ensure qs-weapons-on-back
ensure qs-crafting

6. ESX VERSION

To correctly run the resource on ESX we will first have to go to qs-core/config/config.lua and add our identifier system here, that is, license or steam.
It is important to change the QSConfig.Multicharacter to true whenever we use a multicharacter or do not have prefixes in our identifier. For example, ESX Legacy does not use a prefix, so QSConfig.Multicharacter should be true.
Basic localization of framework configs for qs-core.
If you use a custom ESX system, with renamed names or events you can access config_framework.lua in any of the three resources, qs-core, qs-inventory or qs-shops and add your modifications here.
Location of important settings for framework.

7. CONSUMIBLES

Consumable items are inside other resources, which use ESX.RegisterUsableItems, such as esx_basicneeds.
These resources should always be placed below our QS scripts, otherwise they cannot be used or consumed.
Never place consumables on top of QS scripts, otherwise you won't be able to use them.
ensure qs-core
ensure qs-inventory
ensure qs-shops
ensure progressbar
ensure qs-backpack
ensure qs-weapons-on-back
ensure qs-crafting
ensure esx_basicneeds
ensure esx_ambulancejob
ensure esx_drugeffects

7. GENERAL ITEMS

From now on, all your database scripts inside the items table will no longer work if you use Quasar Inventory.
Now all of your resources will be inside qs-core/config/config_items.lua, and will be added just like the examples you'll find there.
Learn how to add items and how to migrate your items from the items table to qs-core in the Snippets section.

8. ES_EXTENDED MODIFICATIONS

DISABLE INVENTORY

es_extended/client/main.lua (Remove this function)

First we must disable the function that opens the original ESX inventory, we will do this in a very simple way, we will eliminate this function completely
ESX 1.2
ESX 1 Final
Extended Mode
ESX Legacy
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
if IsControlJustReleased(0, 289) then
if IsInputDisabled(0) and not isDead and not ESX.UI.Menu.IsOpen('default', 'es_extended', 'inventory') then
ESX.ShowInventory()
end
end
end
end)
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
if IsControlJustReleased(0, 289) then
if IsInputDisabled(0) and not isDead and not ESX.UI.Menu.IsOpen('default', 'es_extended', 'inventory') then
ESX.ShowInventory()
end
end
end
end)
CreateThread(function()
while true do
Wait(0)
if IsControlJustReleased(0, 289) then
if IsInputDisabled(0) and not isDead and not ESX.UI.Menu.IsOpen('default', 'es_extended', 'inventory') then
ESX.ShowInventory()
end
end
end
end)
In ESX Legacy, you will be able to disable the basic ESX inventory simply by modifying the config.lua of your es_extended.

es_extended/server/main.lua (Remove this function)

Look for a similar code and eliminate it completely, with this we avoid WARNING that spam our console.
This step is not mandatory, but we avoid the WARNING of the old ESX inventory.
ESX 1.2
ESX 1 FInal
Extended Mode
ESX Legacy
if result[1].inventory and result[1].inventory ~= '' then
local inventory = json.decode(result[1].inventory)
for name,count in pairs(inventory) do
local item = ESX.Items[name]
if item then
foundItems[name] = count
else
print(('[^3WARNING^7] Ignoring invalid item "%s" for "%s"'):format(name, identifier))
end
end
end
for name,item in pairs(ESX.Items) do
local count = foundItems[name] or 0
if count > 0 then userData.weight = userData.weight + (item.weight * count) end
table.insert(userData.inventory, {
name = name,
count = count,
label = item.label,
weight = item.weight,
usable = ESX.UsableItemsCallbacks[name] ~= nil,
rare = item.rare,
canRemove = item.canRemove
})
end
table.sort(userData.inventory, function(a, b)
return a.label < b.label
end)
if result[1].inventory and result[1].inventory ~= '' then
local inventory = json.decode(result[1].inventory)
for name,count in pairs(inventory) do
local item = ESX.Items[name]
if item then
foundItems[name] = count
else
print(('[^3WARNING^7] Ignoring invalid item "%s" for "%s"'):format(name, identifier))
end
end
end
for name,item in pairs(ESX.Items) do
local count = foundItems[name] or 0
if count > 0 then userData.weight = userData.weight + (item.weight * count) end
table.insert(userData.inventory, {
name = name,
count = count,
label = item.label,
weight = item.weight,
usable = ESX.UsableItemsCallbacks[name] ~= nil,
rare = item.rare,
canRemove = item.canRemove
})
end
table.sort(userData.inventory, function(a, b)
return a.label < b.label
end)
if result[1].inventory and result[1].inventory ~= '' then
local inventory = json.decode(result[1].inventory)
for name,count in pairs(inventory) do
local item = ESX.Items[name]
if item then
foundItems[name] = count
else
print(('[^3WARNING^7] Ignoring invalid item "%s" for "%s"'):format(name, identifier))
end
end
end
for name,item in pairs(ESX.Items) do
local count = foundItems[name] or 0
if count > 0 then userData.weight = userData.weight + (item.weight * count) end
table.insert(userData.inventory, {
name = name,
count = count,
label = item.label,
weight = item.weight,
usable = ESX.UsableItemsCallbacks[name] ~= nil,
rare = item.rare,
canRemove = item.canRemove
})
end
table.sort(userData.inventory, function(a, b)
return a.label < b.label
end)
if result.inventory and result.inventory ~= '' then
local inventory = json.decode(result.inventory)
for name,count in pairs(inventory) do
local item = ESX.Items[name]
if item then
foundItems[name] = count
else
print(('[^3WARNING^7] Ignoring invalid item "%s" for "%s"'):format(name, identifier))
end
end
end
for name,item in pairs(ESX.Items) do
local count = foundItems[name] or 0
if count > 0 then userData.weight = userData.weight + (item.weight * count) end
table.insert(userData.inventory, {
name = name,
count = count,
label = item.label,
weight = item.weight,
usable = Core.UsableItemsCallbacks[name] ~= nil,
rare = item.rare,
canRemove = item.canRemove
})
end
table.sort(userData.inventory, function(a, b)
return a.label < b.label
end)

PLAYER DATA

es_extended/client/main.lua (Add this function)
We add this in client/main.lua at the bottom for optimal compatibility.
RegisterNetEvent('qs-core:setPlayerData')
AddEventHandler('qs-core:setPlayerData', function(data)
local Inventory = data.items
for _, slot in pairs(Inventory) do
Inventory[_].count = Inventory[_].amount
end
ESX.PlayerData.inventory = Inventory
end)

SAVING PLAYERS AND INVENTORY

es_extended/server/functions.lua (Replace this functions)

Recently, in patch 1.2, we added a new feature that saves players from time to time, along with their inventory, since previously, inventory was only saved when leaving the server.
For this function to work properly, please modify this.
Modify ESX.SavePlayer.
ESX 1.2
ESX 1 Final
Extended Mode
ESX Legacy Old
ESX Legacy New
ESX.SavePlayer = function(xPlayer, cb)
local asyncTasks = {}
-- User accounts
for k,v in ipairs(xPlayer.accounts) do
if ESX.LastPlayerData[xPlayer.source].accounts[v.name] ~= v.money then
table.insert(asyncTasks, function(cb)
MySQL.Async.execute('UPDATE user_accounts SET money = @money WHERE identifier = @identifier AND name = @name', {
['@money'] = v.money,
['@identifier'] = xPlayer.identifier,
['@name'] = v.name
}, function(rowsChanged)
cb()
end)
end)
ESX.LastPlayerData[xPlayer.source].accounts[v.name] = v.money
end
end
-- Job, loadout, inventory and position
table.insert(asyncTasks, function(cb)
MySQL.Async.execute('UPDATE users SET job = @job, job_grade = @job_grade, loadout = @loadout, position = @position, WHERE identifier = @identifier', {
['@job'] = xPlayer.job.name,
['@job_grade'] = xPlayer.job.grade,
['@loadout'] = json.encode(xPlayer.getLoadout()),
['@position'] = json.encode(xPlayer.getCoords()),
['@identifier'] = xPlayer.identifier,
}, function(rowsChanged)
cb()
end)
end)
Async.parallel(asyncTasks, function(results)
print(('[es_extended] [^2INFO^7] Saved player "%s^7"'):format(xPlayer.getName()))
if cb ~= nil then
cb()
end
end)
end
ESX.SavePlayer = function(xPlayer, cb)
local asyncTasks = {}
table.insert(asyncTasks, function(cb2)
MySQL.Async.execute('UPDATE users SET accounts = @accounts, job = @job, job_grade = @job_grade, `group` = @group, loadout = @loadout, position = @position WHERE identifier = @identifier', {
['@accounts'] = json.encode(xPlayer.getAccounts(true)),
['@job'] = xPlayer.job.name,
['@job_grade'] = xPlayer.job.grade,
['@group'] = xPlayer.getGroup(),
['@loadout'] = json.encode(xPlayer.getLoadout(true)),
['@position'] = json.encode(xPlayer.getCoords()),
['@identifier'] = xPlayer.getIdentifier(),
}, function(rowsChanged)
cb2()
end)
end)
Async.parallel(asyncTasks, function(results)
print(('[es_extended] [^2INFO^7] Saved player "%s^7"'):format(xPlayer.getName()))
if cb then
cb()
end
end)
end
ESX.SavePlayer = function(xPlayer, cb)
if ExM.DatabaseType == "es+esx" then
-- Nothing yet :wink:
elseif ExM.DatabaseType == "newesx" then
MySQL.Async.execute('UPDATE users SET accounts = @accounts, job = @job, job_grade = @job_grade, `group` = @group, loadout = @loadout, position = @position WHERE identifier = @identifier', {
['@accounts'] = json.encode(xPlayer.getAccounts(true)),
['@job'] = xPlayer.job.name,
['@job_grade'] = xPlayer.job.grade,
['@group'] = xPlayer.getGroup(),
['@loadout'] = json.encode(xPlayer.getLoadout(true)),
['@position'] = json.encode(xPlayer.getCoords()),
['@identifier'] = xPlayer.getIdentifier(),
}, cb)
end
end
local savePlayers = -1
Citizen.CreateThread(function()
savePlayers = MySQL.Sync.store("UPDATE users SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ? WHERE `identifier` = ?")
end)
ESX.SavePlayer = function(xPlayer, cb)
local asyncTasks = {}
table.insert(asyncTasks, function(cb2)
MySQL.Async.execute(savePlayers, {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.getGroup(),
json.encode(xPlayer.getCoords()),
xPlayer.getIdentifier()
}, function(rowsChanged)
cb2()
end)
end)
Async.parallel(asyncTasks, function(results)
print(('[^2INFO^7] Saved player ^5"%s^7"'):format(xPlayer.getName()))
if cb then
cb()
end
end)
end
ESX Legacy 1.5/1.6/1.7 or higher.
function Core.SavePlayer(xPlayer, cb)
MySQL.prepare('UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ? WHERE `identifier` = ?', {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.group,
json.encode(xPlayer.getCoords()),
xPlayer.identifier
}, function(affectedRows)
if affectedRows == 1 then
print(('[^2INFO^7] Saved player ^5"%s^7"'):format(xPlayer.name))
end
if cb then cb() end
end)
end
And we must also modify ESX.SavePlayers.
ESX 1.2
ESX 1 Final
Extended Mode
ESX Legacy Old
ESX Legacy New
ESX.SavePlayers = function(cb)
local asyncTasks = {}
local xPlayers = ESX.GetPlayers()
for i=1, #xPlayers, 1 do
table.insert(asyncTasks, function(cb)
local xPlayer = ESX.GetPlayerFromId(xPlayers[i])
ESX.SavePlayer(xPlayer, cb)
TriggerEvent('qs-core:savePlayer', xPlayers[i])
end)
end
Async.parallelLimit(asyncTasks, 8, function(results)
print(('[es_extended] [^2INFO^7] Saved %s player(s)'):format(#xPlayers))
if cb ~= nil then
cb()
end
end)
end
ESX.SavePlayers = function(cb)
local xPlayers, asyncTasks = ESX.GetPlayers(), {}
for i=1, #xPlayers, 1 do
table.insert(asyncTasks, function(cb2)
local xPlayer = ESX.GetPlayerFromId(xPlayers[i])
ESX.SavePlayer(xPlayer, cb2)
TriggerEvent('qs-core:savePlayer', xPlayers[i])
end)
end
Async.parallelLimit(asyncTasks, 8, function(results)
print(('[es_extended] [^2INFO^7] Saved %s player(s)'):format(#xPlayers))
if cb then
cb()
end
end)
end
ESX.SavePlayers = function(finishedCB)
CreateThread(function()
local savedPlayers = 0
local playersToSave = #ESX.Players
local maxTimeout = 20000
local currentTimeout = 0
-- Save Each player
for _, xPlayer in ipairs(ESX.Players) do
ESX.SavePlayer(xPlayer, function(rowsChanged)
if rowsChanged == 1 then
savedPlayers = savedPlayers + 1
end
end)
end
-- Call the callback when done
while true do
Citizen.Wait(500)
currentTimeout = currentTimeout + 500
if playersToSave == savedPlayers then
finishedCB(true)
break
elseif currentTimeout >= maxTimeout then
finishedCB(false)
break
end
end
end)
local xPlayers = ESX.GetPlayers()
for i=1, #xPlayers, 1 do
TriggerEvent('qs-core:savePlayer', xPlayers[i])
end
end
ESX.SavePlayers = function(cb)
local xPlayers = ESX.GetExtendedPlayers()
if #xPlayers > 0 then
local time = os.time()
local selectListWithNames = "SELECT '%s' AS identifier, '%s' AS new_accounts, '%s' AS new_job, %s AS new_job_grade, '%s' AS new_group, '%s' AS new_position "
local selectListNoNames = "SELECT '%s', '%s', '%s' , %s, '%s', '%s' "
local updateCommand = 'UPDATE users u JOIN ('
local selectList = selectListNoNames
local first = true
for k, xPlayer in pairs(xPlayers) do
if first == false then
updateCommand = updateCommand .. ' UNION '
else
selectList = selectListWithNames
end
updateCommand = updateCommand .. string.format(selectList,
xPlayer.identifier,
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.getGroup(),
json.encode(xPlayer.getCoords())
)
first = false
end
updateCommand = updateCommand .. ' ) vals ON u.identifier = vals.identifier SET accounts = new_accounts, job = new_job, job_grade = new_job_grade, `group` = new_group, `position` = new_position'
MySQL.Async.fetchAll(updateCommand, {},
function(result)
if result then
if cb then cb() else print(('[^2INFO^7] Saved %s of %s player(s) over %s seconds'):format(result.affectedRows, #xPlayers, os.time() - time)) end
end
end)
local xPlayers = ESX.GetPlayers()
for i=1, #xPlayers, 1 do
TriggerEvent('qs-core:savePlayer', xPlayers[i])
end
end
end
ESX Legacy 1.5/1.6/1.7 or higher.
function Core.SavePlayers(cb)
local xPlayers = ESX.GetExtendedPlayers()
local count = #xPlayers
if count > 0 then
local parameters = {}
local time = os.time()
for i=1, count do
local xPlayer = xPlayers[i]
parameters[#parameters+1] = {
json.encode(xPlayer.getAccounts(true)),
xPlayer.job.name,
xPlayer.job.grade,
xPlayer.group,
json.encode(xPlayer.getCoords()),
xPlayer.identifier
}
end
MySQL.prepare("UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ? WHERE `identifier` = ?", parameters,
function(results)
if results then
if type(cb) == 'function' then cb() else print(('[^2INFO^7] Saved %s %s over %s ms'):format(count, count > 1 and 'players' or 'player', (os.time() - time) / 1000000)) end
end
end)
local xPlayers = ESX.GetPlayers()
for i=1, #xPlayers, 1 do
TriggerEvent('qs-core:savePlayer', xPlayers[i])
end
end
end

BASIC FUNTIONS

es_extended/server/classes/player.lua (Replace this functions)

These modifications are mandatory, since thanks to this, the inventory will work with the original ESX functions and avoid having to modify each script one by one.
Here we must modify several parts, go modifying each of the functions that I will write below. They seem like a lot but it is simpler than it seems.
ALL VERSIONS
ESX LEGACY OLD
ESX LEGACY NEW
self.addAccountMoney = function(accountName, money)
if money > 0 then
local money = ESX.Math.Round(money)
if accountName == 'money' then
local cash = self.getInventoryItem('cash').count
if cash then
self.addInventoryItem("cash", money)
self.setAccountMoney('money', cash + money)
end
elseif accountName == 'black_money' then
local black_money = self.getInventoryItem('black_money').count
if black_money then
self.addInventoryItem("black_money", money)
self.setAccountMoney('black_money', black_money + money)
end
else
local account = self.getAccount(accountName)
if account and account.money then
local newMoney = account.money + money
self.setAccountMoney(accountName, newMoney)
end
end
end
end
self.addAccountMoney = function(accountName, money)
if money > 0 then
local money = ESX.Math.Round(money)
if accountName == 'money' then
local cash = self.getInventoryItem('cash').count
if cash then
self.addInventoryItem("cash", money)
self.setAccountMoney('money', cash + money)
end
elseif accountName == 'black_money' then
local black_money = self.getInventoryItem('black_money').count
if black_money then
self.addInventoryItem("black_money", money)
self.setAccountMoney('black_money', black_money + money)
end
else
local account = self.getAccount(accountName)
if account and account.money then
local newMoney = account.money + money
self.setAccountMoney(accountName, newMoney)
end
end
end
end
ESX Legacy 1.5/1.6/1.7 or higher.
function self.addAccountMoney(accountName, money)
if money > 0 then
local money = ESX.Math.Round(money)
if accountName == 'money' then
local cash = self.getInventoryItem('cash').count
if cash then
self.addInventoryItem("cash", money)
self.setAccountMoney('money', cash + money)
end
elseif accountName == 'black_money' then
local black_money = self.getInventoryItem('black_money').count
if black_money then
self.addInventoryItem("black_money", money)
self.setAccountMoney('black_money', black_money + money)
end
else
local account = self.getAccount(accountName)
if account and account.money then
local newMoney = account.money + money
self.setAccountMoney(accountName, newMoney)
end
end
end
end
ALL VERSIONS
ESX LEGACY OLD
ESX LEGACY NEW
self.removeAccountMoney = function(accountName, money)
if money > 0 then
local money = ESX.Math.Round(money)
if accountName == 'money' then
local cash = self.getInventoryItem('cash').count
if cash then
self.removeInventoryItem("cash", money)
local newMoney = cash - money
if newMoney >= 0 then
self.setAccountMoney('money', newMoney)
else
self.setAccountMoney('money', 0)
end
end
elseif accountName == 'black_money' then
local black_money = self.getInventoryItem('black_money').count
if black_money then
self.removeInventoryItem("black_money", money)
local newMoney = black_money - money
if newMoney >= 0 then
self.setAccountMoney('black_money', newMoney)
else
self.setAccountMoney('black_money', 0)
end
end
else
local account = self.getAccount(accountName)
if account and account.money then
local newMoney = account.money - money
if newMoney >= 0 then
self.setAccountMoney(accountName, newMoney)
else
self.setAccountMoney(accountName, 0)
end
end
end
end
end
self.removeAccountMoney = function(accountName, money)
if money > 0 then
local money = ESX.Math.Round(money)
if accountName == 'money' then
local cash = self.getInventoryItem('cash').count
if cash then
self.removeInventoryItem("cash", money)
local newMoney = cash - money
if newMoney >= 0 then
self.setAccountMoney('money', newMoney)
else
self.setAccountMoney('money', 0)
end
end
elseif accountName == 'black_money' then
local black_money = self.getInventoryItem('black_money').count
if black_money then
self.removeInventoryItem("black_money", money)
local newMoney = black_money - money
if newMoney >= 0 then
self.setAccountMoney('black_money', newMoney)
else
self.setAccountMoney('black_money', 0)
end
end
else
local account = self.getAccount(accountName)
if account and account.money then
local newMoney = account.money - money
if newMoney >= 0 then
self.setAccountMoney(accountName, newMoney)
else
self.setAccountMoney(accountName, 0)
end
end
end
end
end
ESX Legacy 1.5/1.6/1.7 or higher.
function self.removeAccountMoney(accountName, money)
if money > 0 then
local money = ESX.Math.Round(money)
if accountName == 'money' then
local cash = self.getInventoryItem('cash').count
if cash then
self.removeInventoryItem("cash", money)
local newMoney = cash - money
if newMoney >= 0 then
self.setAccountMoney('money', newMoney)
else
self.setAccountMoney('money', 0)
end
end
elseif accountName == 'black_money' then
local black_money = self.getInventoryItem('black_money').count
if black_money then
self.