Basic configuration
2
Last updated
Go to the path es_estended/server/main.lua and replace this functions
Important: You must replace, not add two identical controllers
Go to the path es_estended/server/modules/commands.lua and replace this command
Add this function anywhere in the file es_estended/server/functions.lua
Important: You must add this function here, as it does not exist by default
Go to the following path qb-core/server/functions.lua and add this function
Replace this command in the following path: qb-core/server/commands.lua
Important: You must replace, not add two identical controllers
Go to the following path qbx_core/server/functions.lua and add this function
Replace this function in the following path: qbx_core/server/storage/players.lua
Replace this function in the following path: qbx_core/server/player.lua
Add this functions in the following path: qbx_core/bridge/qb/server/functions.lua
Replace this command in the following path: qbx_core/server/commands.lua
If you are going to assign undefined ranges, make sure to add the new group in the other scripts, for example:
Config.StaffGroups = {
esx = { 'admin', 'superadmin', 'owner' },
qbcore = { 'admin', 'god' },
qbox = { 'admin', 'god' }
}If you use the owner rank, you must add it to the scripts
💻 If you have trouble with the installation, there is a video installation tutorial available
Last updated
local function createESXPlayer(identifier, playerId, data)
local accounts = {}
for account, money in pairs(Config.StartingAccountMoney) do
accounts[account] = money
end
local defaultGroup = "user"
if Core.IsPlayerAdmin(playerId) then
print(("[^2INFO^0] Player ^5%s^0 Has been granted admin permissions via ^5Ace Perms^7."):format(playerId))
defaultGroup = "admin"
end
local allowedGroups = ESX.GetAllowedGroups()
if allowedGroups then
if not allowedGroups[defaultGroup] then
defaultGroup = allowedGroups["user"] and "user" or nil
if not defaultGroup then
for g in pairs(allowedGroups) do defaultGroup = g break end
end
end
end
defaultGroup = defaultGroup or "user"
local parameters = Config.Multichar and
{ json.encode(accounts), identifier, Core.generateSSN(), defaultGroup, data.firstname, data.lastname, data.dateofbirth, data.sex, data.height }
or { json.encode(accounts), identifier, Core.generateSSN(), defaultGroup }
if Config.StartingInventoryItems then
table.insert(parameters, json.encode(Config.StartingInventoryItems))
end
MySQL.prepare(newPlayer, parameters, function()
loadESXPlayer(identifier, playerId, true)
end)
endfunction loadESXPlayer(identifier, playerId, isNew)
local userData = {
accounts = {},
inventory = {},
loadout = {},
weight = 0,
name = GetPlayerName(playerId),
identifier = identifier,
firstName = "John",
lastName = "Doe",
dateofbirth = "01/01/2000",
height = 120,
dead = false,
}
local result = MySQL.prepare.await(loadPlayer, { identifier })
if not result then
print(("[^1ERROR^0] No se encontró el jugador en la base de datos o la consulta falló: %s"):format(identifier))
return
end
-- Accounts
local accounts = result.accounts
accounts = (accounts and accounts ~= "") and json.decode(accounts) or {}
for account, data in pairs(Config.Accounts) do
data.round = data.round or data.round == nil
local index = #userData.accounts + 1
userData.accounts[index] = {
name = account,
money = accounts[account] or Config.StartingAccountMoney[account] or 0,
label = data.label,
round = data.round,
index = index,
}
end
-- SSN
userData.ssn = result.ssn
-- Job
local job, grade = result.job, tostring(result.job_grade)
if not ESX.DoesJobExist(job, grade) then
print(("[^3WARNING^7] Ignoring invalid job for ^5%s^7 [job: ^5%s^7, grade: ^5%s^7]"):format(identifier, job, grade))
job, grade = "unemployed", "0"
end
local jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade]
userData.job = {
id = jobObject.id,
name = jobObject.name,
label = jobObject.label,
type = jobObject.type,
grade = tonumber(grade),
grade_name = gradeObject.name,
grade_label = gradeObject.label,
grade_salary = gradeObject.salary,
skin_male = gradeObject.skin_male and json.decode(gradeObject.skin_male) or {},
skin_female = gradeObject.skin_female and json.decode(gradeObject.skin_female) or {},
}
-- Inventory
if not Config.CustomInventory then
local inventory = (result.inventory and result.inventory ~= "") and json.decode(result.inventory) or {}
for name, item in pairs(ESX.Items) do
local count = inventory[name] or 0
userData.weight += (count * item.weight)
userData.inventory[#userData.inventory + 1] = {
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)
elseif result.inventory and result.inventory ~= "" then
userData.inventory = json.decode(result.inventory)
end
if result.group then
userData.group = result.group
else
userData.group = "user"
end
local allowedGroups = ESX.GetAllowedGroups()
if allowedGroups and not allowedGroups[userData.group] then
userData.group = "user"
end
-- Loadout
if not Config.CustomInventory then
if result.loadout and result.loadout ~= "" then
local loadout = json.decode(result.loadout)
for name, weapon in pairs(loadout) do
local label = ESX.GetWeaponLabel(name)
if label then
userData.loadout[#userData.loadout + 1] = {
name = name,
ammo = weapon.ammo,
label = label,
components = weapon.components or {},
tintIndex = weapon.tintIndex or 0,
}
end
end
end
end
-- Position
userData.coords = json.decode(result.position) or Config.DefaultSpawns[ESX.Math.Random(1,#Config.DefaultSpawns)]
-- Skin
userData.skin = (result.skin and result.skin ~= "") and json.decode(result.skin) or { sex = userData.sex == "f" and 1 or 0 }
-- Metadata
userData.metadata = (result.metadata and result.metadata ~= "") and json.decode(result.metadata) or {}
-- xPlayer Creation
local xPlayer = CreateExtendedPlayer(playerId, identifier, userData.ssn, userData.group, userData.accounts, userData.inventory, userData.weight, userData.job, userData.loadout, GetPlayerName(playerId), userData.coords, userData.metadata)
GlobalState["playerCount"] = GlobalState["playerCount"] + 1
ESX.Players[playerId] = xPlayer
Core.playersByIdentifier[identifier] = xPlayer
-- Identity
if result.firstname and result.firstname ~= "" then
userData.firstName = result.firstname
userData.lastName = result.lastname
local name = ("%s %s"):format(result.firstname, result.lastname)
userData.name = name
xPlayer.set("firstName", result.firstname)
xPlayer.set("lastName", result.lastname)
xPlayer.setName(name)
if result.dateofbirth then
userData.dateofbirth = result.dateofbirth
xPlayer.set("dateofbirth", result.dateofbirth)
end
if result.sex then
userData.sex = result.sex
xPlayer.set("sex", result.sex)
end
if result.height then
userData.height = result.height
xPlayer.set("height", result.height)
end
end
TriggerEvent("esx:playerLoaded", playerId, xPlayer, isNew)
userData.money = xPlayer.getMoney()
userData.maxWeight = xPlayer.getMaxWeight()
userData.variables = xPlayer.variables or {}
xPlayer.triggerEvent("esx:playerLoaded", userData, isNew, userData.skin)
if not Config.CustomInventory then
xPlayer.triggerEvent("esx:createMissingPickups", Core.Pickups)
elseif setPlayerInventory then
setPlayerInventory(playerId, xPlayer, userData.inventory, isNew)
end
xPlayer.triggerEvent("esx:registerSuggestions", Core.RegisteredCommands)
print(('[^2INFO^0] Player ^5"%s"^0 has connected to the server. ID: ^5%s^7'):format(xPlayer.getName(), playerId))
end
ESX.RegisterCommand(
"setgroup",
"admin",
function(xPlayer, args)
if not args.playerId then
args.playerId = xPlayer.source
end
local allowedGroups = ESX.GetAllowedGroups()
if allowedGroups and not allowedGroups[args.group] then
local list = {}
for g in pairs(allowedGroups) do list[#list + 1] = g end
table.sort(list)
if xPlayer and xPlayer.source then
TriggerClientEvent("esx:showNotification", xPlayer.source, ("Grupo no válido. Permitidos: %s"):format(table.concat(list, ", ")))
else
print(("[^1ERROR^0] Invalid group. Allowed: %s"):format(table.concat(list, ", ")))
end
return
end
args.playerId.setGroup(args.group)
if Config.AdminLogging then
ESX.DiscordLogFields("UserActions", "/setgroup Triggered!", "pink", {
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
{ name = "Target", value = args.playerId.name, inline = true },
{ name = "Group", value = args.group, inline = true },
})
end
end,
true,
{
help = TranslateCap("command_setgroup"),
validate = true,
arguments = {
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
{ name = "group", help = TranslateCap("command_setgroup_group"), type = "string" },
},
}
)---@return table|nil set groups (groups = true) o nil
function ESX.GetAllowedGroups()
local success, rows = pcall(MySQL.query.await, "SELECT grupo FROM jotadev_admin_groups", {})
if not success or not rows or #rows == 0 then return nil end
local set = {}
for _, r in ipairs(rows) do
if r.grupo and r.grupo ~= "" then set[r.grupo] = true end
end
return next(set) and set or nil
end---@return table|nil set of groups (group = true) o nil
function QBCore.Functions.GetAllowedGroups()
local success, rows = pcall(MySQL.query.await, "SELECT grupo FROM jotadev_admin_groups", {})
if not success or not rows or #rows == 0 then return nil end
local set = {}
for _, r in ipairs(rows) do
if r.grupo and r.grupo ~= "" then set[r.grupo] = true end
end
return next(set) and set or nil
end---Set the player's admin group
---@param source any
---@param group string
function QBCore.Functions.SetPlayerGroup(source, group)
if not source or source == 0 or not group or type(group) ~= "string" then return end
group = (tostring(group)):gsub("^%s+", ""):gsub("%s+$", ""):lower()
if group == "" then return end
local allowed = QBCore.Functions.GetAllowedGroups()
if allowed and not allowed[group] then return end
local license = QBCore.Functions.GetIdentifier(source, "license")
if not license or license == "" then return end
local ok = pcall(function()
MySQL.update.await("UPDATE players SET admin_group = ? WHERE license = ?", { group, license })
end)
if not ok then return end
local Player = QBCore.Functions.GetPlayer(source)
if Player and Player.PlayerData then
Player.PlayerData.admin_group = group
end
QBCore.Functions.AddPermission(source, group)
end---Get the player's admin group
---@param source any
---@return string|nil
function QBCore.Functions.GetPlayerGroup(source)
local Player = QBCore.Functions.GetPlayer(source)
if not Player or not Player.PlayerData then return nil end
local g = Player.PlayerData.admin_group
if type(g) == "string" and g:gsub("^%s+", ""):gsub("%s+$", "") ~= "" then return (g:gsub("^%s+", ""):gsub("%s+$", "")):lower() end
return nil
end
QBCore.Commands.Add('addpermission', Lang:t('command.addpermission.help'), { { name = Lang:t('command.addpermission.params.id.name'), help = Lang:t('command.addpermission.params.id.help') }, { name = Lang:t('command.addpermission.params.permission.name'), help = Lang:t('command.addpermission.params.permission.help') } }, true, function(source, args)
local Player = QBCore.Functions.GetPlayer(tonumber(args[1]))
local permission = tostring(args[2]):lower()
local allowedGroups = QBCore.Functions.GetAllowedGroups()
if allowedGroups and not allowedGroups[permission] then
local list = {}
for g in pairs(allowedGroups) do list[#list + 1] = g end
table.sort(list)
TriggerClientEvent('QBCore:Notify', source, ('Invalid group. Allowed: %s'):format(table.concat(list, ", ")), 'error')
return
end
if Player then
QBCore.Functions.SetPlayerGroup(Player.PlayerData.source, permission)
else
TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error')
end
end, 'god')---@param source Source
---@return string|nil
function GetPlayerGroup(source)
local Player = GetPlayer(source)
if not Player or not Player.PlayerData then return nil end
local g = Player.PlayerData.admin_group
if type(g) == 'string' and g:gsub('^%s+', ''):gsub('%s+$', '') ~= '' then
return (g:gsub('^%s+', ''):gsub('%s+$', '')):lower()
end
return nil
end
exports('GetPlayerGroup', GetPlayerGroup)
---@param source Source
---@param group string
function SetPlayerGroup(source, group)
if not source or source == 0 or not group or type(group) ~= 'string' then return end
group = (tostring(group)):gsub('^%s+', ''):gsub('%s+$', ''):lower()
if group == '' then return end
local allowed = GetAllowedGroups()
if allowed and not allowed[group] then return end
local license = GetPlayerIdentifierByType(source --[[@as string]], 'license2') or GetPlayerIdentifierByType(source --[[@as string]], 'license')
if not license or license == '' then return end
local ok = pcall(function()
MySQL.update.await('UPDATE players SET admin_group = ? WHERE license = ?', { group, license })
end)
if not ok then return end
local Player = GetPlayer(source)
if Player and Player.PlayerData then
Player.PlayerData.admin_group = group
end
AddPermission(source, group)
end
exports('SetPlayerGroup', SetPlayerGroup)
AddEventHandler('QBCore:Server:PlayerLoaded', function(player)
if player and player.PlayerData and player.PlayerData.admin_group and player.PlayerData.admin_group ~= '' then
AddPermission(player.PlayerData.source, player.PlayerData.admin_group)
end
end)
---@return table|nil
function GetAllowedGroups()
local success, rows = pcall(MySQL.query.await, 'SELECT grupo FROM jotadev_admin_groups', {})
if not success or not rows or #rows == 0 then return nil end
local set = {}
for _, r in ipairs(rows) do
if r.grupo and r.grupo ~= '' then set[r.grupo] = true end
end
return next(set) and set or nil
end
exports('GetAllowedGroups', GetAllowedGroups)---@param license2 string
---@param license? string
---@return PlayerEntity[]
local function fetchAllPlayerEntities(license2, license)
---@type PlayerEntity[]
local chars = {}
---@type PlayerEntityDatabase[]
local result = MySQL.query.await('SELECT citizenid, charinfo, money, job, gang, position, metadata, admin_group, UNIX_TIMESTAMP(last_logged_out) AS lastLoggedOutUnix FROM players WHERE license = ? OR license = ? ORDER BY cid', {license, license2})
for i = 1, #result do
chars[i] = result[i]
chars[i].charinfo = json.decode(result[i].charinfo)
chars[i].money = json.decode(result[i].money)
chars[i].job = result[i].job and json.decode(result[i].job)
chars[i].gang = result[i].gang and json.decode(result[i].gang)
chars[i].position = convertPosition(result[i].position)
chars[i].metadata = json.decode(result[i].metadata)
chars[i].admin_group = result[i].admin_group
chars[i].lastLoggedOut = result[i].lastLoggedOutUnix
end
return chars
end---@param citizenId string
---@return PlayerEntity?
local function fetchPlayerEntity(citizenId)
---@type PlayerEntityDatabase
local player = MySQL.single.await('SELECT userId, citizenid, license, name, charinfo, money, job, gang, position, metadata, admin_group, UNIX_TIMESTAMP(last_logged_out) AS lastLoggedOutUnix FROM players WHERE citizenid = ?', { citizenId })
local charinfo = player and json.decode(player.charinfo)
return player and {
userId = player.userId,
citizenid = player.citizenid,
license = player.license,
name = player.name,
money = json.decode(player.money),
charinfo = charinfo,
cid = charinfo.cid,
job = player.job and json.decode(player.job),
gang = player.gang and json.decode(player.gang),
position = convertPosition(player.position),
metadata = json.decode(player.metadata),
admin_group = player.admin_group,
lastLoggedOut = player.lastLoggedOutUnix
} or nil
end---@param source? integer if player is online
---@param playerData? PlayerEntity|PlayerData
---@return Player player
function CheckPlayerData(source, playerData)
playerData = playerData or {}
---@diagnostic disable-next-line: param-type-mismatch
local playerState = Player(source)?.state
local Offline = true
if source then
playerData.source = source
playerData.license = playerData.license or GetPlayerIdentifierByType(source --[[@as string]], 'license2') or GetPlayerIdentifierByType(source --[[@as string]], 'license')
playerData.name = GetPlayerName(source)
Offline = false
end
playerData.userId = playerData.userId or nil
playerData.citizenid = playerData.citizenid or GenerateUniqueIdentifier('citizenid')
playerData.cid = playerData.charinfo?.cid or playerData.cid or 1
playerData.money = playerData.money or {}
for moneytype, startamount in pairs(config.money.moneyTypes) do
playerData.money[moneytype] = playerData.money[moneytype] or startamount
end
-- Charinfo
playerData.charinfo = playerData.charinfo or {}
playerData.charinfo.firstname = playerData.charinfo.firstname or 'Firstname'
playerData.charinfo.lastname = playerData.charinfo.lastname or 'Lastname'
playerData.charinfo.birthdate = playerData.charinfo.birthdate or '00-00-0000'
playerData.charinfo.gender = playerData.charinfo.gender or 0
playerData.charinfo.backstory = playerData.charinfo.backstory or 'placeholder backstory'
playerData.charinfo.nationality = playerData.charinfo.nationality or 'USA'
playerData.charinfo.phone = playerData.charinfo.phone or GenerateUniqueIdentifier('PhoneNumber')
playerData.charinfo.account = playerData.charinfo.account or GenerateUniqueIdentifier('AccountNumber')
playerData.charinfo.cid = playerData.charinfo.cid or playerData.cid
-- Metadata
playerData.metadata = playerData.metadata or {}
playerData.metadata.optin = playerData.metadata.optin and true or false
playerData.metadata.health = playerData.metadata.health or 200
playerData.metadata.hunger = playerData.metadata.hunger or 100
playerData.metadata.thirst = playerData.metadata.thirst or 100
playerData.metadata.stress = playerData.metadata.stress or 0
if playerState then
playerState:set('hunger', playerData.metadata.hunger, true)
playerState:set('thirst', playerData.metadata.thirst, true)
playerState:set('stress', playerData.metadata.stress, true)
end
playerData.metadata.isdead = playerData.metadata.isdead or false
playerData.metadata.inlaststand = playerData.metadata.inlaststand or false
playerData.metadata.armor = playerData.metadata.armor or 0
playerData.metadata.ishandcuffed = playerData.metadata.ishandcuffed or false
playerData.metadata.tracker = playerData.metadata.tracker or false
playerData.metadata.injail = playerData.metadata.injail or 0
playerData.metadata.jailitems = playerData.metadata.jailitems or {}
playerData.metadata.status = playerData.metadata.status or {}
playerData.metadata.phone = playerData.metadata.phone or {}
playerData.metadata.bloodtype = playerData.metadata.bloodtype or config.player.bloodTypes[math.random(1, #config.player.bloodTypes)]
playerData.metadata.dealerrep = playerData.metadata.dealerrep or 0
playerData.metadata.craftingrep = playerData.metadata.craftingrep or 0
playerData.metadata.attachmentcraftingrep = playerData.metadata.attachmentcraftingrep or 0
playerData.metadata.currentapartment = playerData.metadata.currentapartment or nil
playerData.metadata.jobrep = playerData.metadata.jobrep or {}
playerData.metadata.jobrep.tow = playerData.metadata.jobrep.tow or 0
playerData.metadata.jobrep.trucker = playerData.metadata.jobrep.trucker or 0
playerData.metadata.jobrep.taxi = playerData.metadata.jobrep.taxi or 0
playerData.metadata.jobrep.hotdog = playerData.metadata.jobrep.hotdog or 0
playerData.metadata.callsign = playerData.metadata.callsign or 'NO CALLSIGN'
playerData.metadata.fingerprint = playerData.metadata.fingerprint or GenerateUniqueIdentifier('FingerId')
playerData.metadata.walletid = playerData.metadata.walletid or GenerateUniqueIdentifier('WalletId')
playerData.metadata.criminalrecord = playerData.metadata.criminalrecord or {
hasRecord = false,
date = nil
}
playerData.metadata.licences = playerData.metadata.licences or {
id = true,
driver = true,
weapon = false,
}
playerData.metadata.inside = playerData.metadata.inside or {
house = nil,
apartment = {
apartmentType = nil,
apartmentId = nil,
}
}
playerData.metadata.phonedata = playerData.metadata.phonedata or {
SerialNumber = GenerateUniqueIdentifier('SerialNumber'),
InstalledApps = {},
}
local jobs, gangs = storage.fetchPlayerGroups(playerData.citizenid)
local job = GetJob(playerData.job?.name) or GetJob('unemployed')
assert(job ~= nil, 'Unemployed job not found. Does it exist in shared/jobs.lua?')
local jobGrade = GetJob(playerData.job?.name) and playerData.job.grade.level or 0
if not job.grades[jobGrade] then
jobGrade = 0
end
playerData.job = {
name = playerData.job?.name or 'unemployed',
label = job.label,
payment = job.grades[jobGrade].payment or 0,
type = job.type,
onduty = playerData.job?.onduty or false,
isboss = job.grades[jobGrade].isboss or false,
bankAuth = job.grades[jobGrade].bankAuth or false,
grade = {
name = job.grades[jobGrade].name,
level = jobGrade,
}
}
if QBX.Shared.ForceJobDefaultDutyAtLogin and (job.defaultDuty ~= nil) then
playerData.job.onduty = job.defaultDuty
end
playerData.jobs = jobs or {}
local gang = GetGang(playerData.gang?.name) or GetGang('none')
assert(gang ~= nil, 'none gang not found. Does it exist in shared/gangs.lua?')
local gangGrade = GetGang(playerData.gang?.name) and playerData.gang.grade.level or 0
playerData.gang = {
name = playerData.gang?.name or 'none',
label = gang.label,
isboss = gang.grades[gangGrade].isboss or false,
bankAuth = gang.grades[gangGrade].bankAuth or false,
grade = {
name = gang.grades[gangGrade].name,
level = gangGrade
}
}
playerData.gangs = gangs or {}
playerData.position = playerData.position or defaultSpawn
playerData.items = {}
return CreatePlayer(playerData --[[@as PlayerData]], Offline)
endfunction functions.GetPlayerGroup(source)
return exports.qbx_core:GetPlayerGroup(source)
end
function functions.SetPlayerGroup(source, groupName)
return exports.qbx_core:SetPlayerGroup(source, groupName)
end
function functions.GetAllowedGroups()
return exports.qbx_core:GetAllowedGroups()
endlib.addCommand('addpermission', {
help = locale('command.addpermission.help'),
params = {
{ name = locale('command.addpermission.params.id.name'), help = locale('command.addpermission.params.id.help'), type = 'playerId' },
{ name = locale('command.addpermission.params.permission.name'), help = locale('command.addpermission.params.permission.help'), type = 'string' }
},
restricted = 'group.admin'
}, function(source, args)
if not IsOptin(source) then Notify(source, locale('error.not_optin'), 'error') return end
local player = GetPlayer(args[locale('command.addpermission.params.id.name')])
local permission = tostring(args[locale('command.addpermission.params.permission.name')]):lower()
local allowedGroups = GetAllowedGroups()
if allowedGroups and not allowedGroups[permission] then
local list = {}
for g in pairs(allowedGroups) do list[#list + 1] = g end
table.sort(list)
Notify(source, ('Invalid group. Allowed: %s'):format(table.concat(list, ', ')), 'error')
return
end
if player then
AddPermission(player.PlayerData.source, permission)
else
Notify(source, locale('error.not_online'), 'error')
end
end)add_ace resource.jc_admin command.ensure allow
add_ace resource.jc_admin command.restart allow
add_ace resource.jc_admin command.refresh allow
add_ace resource.jc_admin command.start allow
add_ace resource.jc_admin command.stop allow