Main/resources/[standalone]/utility_lib/server/native.lua

956 lines
28 KiB
Lua
Raw Normal View History

2025-06-07 08:51:21 +02:00
--// Improved/Custom Native //--
StartESX = function(triggerName)
ESX = exports["es_extended"]:getSharedObject()
end
StartQB = function(triggerName)
QBCore = exports['qb-core']:GetCoreObject()
end
ShowNotification = function(source, msg, type)
if GetResourceState("qb-core") == "started" then
TriggerClientEvent('QBCore:Notify', source, msg, type)
elseif GetResourceState("es_extended") == "started" then
TriggerClientEvent('esx:showNotification', source, msg)
end
end
CreateLoop = function(_function, tickTime)
Citizen.CreateThread(function()
local active = true
_break = function()
active = false
end
while active do
_function()
Citizen.Wait(tickTime or 5)
end
end)
end
--// Player //--
-- Item
AddItem = function(source, ...)
if ESX then
xPlayer = ESX.GetPlayerFromId(source)
xPlayer.addInventoryItem(...)
else
xPlayer = QBCore.Functions.GetPlayer(source)
xPlayer.Functions.AddItem(...)
end
end
RemoveItem = function(source, ...)
if ESX then
xPlayer = ESX.GetPlayerFromId(source)
xPlayer.removeInventoryItem(...)
else
xPlayer = QBCore.Functions.GetPlayer(source)
xPlayer.Functions.RemoveItem(...)
end
end
GetItem = function(source, ...)
if ESX then
xPlayer = ESX.GetPlayerFromId(source)
return xPlayer.getInventoryItem(...)
else
xPlayer = QBCore.Functions.GetPlayer(source)
return xPlayer.Functions.GetItemByName(...)
end
end
HaveItem = function(source, ...)
local item = GetItem(source, ...)
if not item then
return false
end
if ESX then
return item.count > 0
else
return item.amount > 0
end
end
HaveItemQuantity = function(source, item, quantity)
local item = GetItem(source, item)
if not item then
return false
end
if ESX then
return item.count > quantity
else
return item.amount > quantity
end
end
-- Money
AddMoney = function(source, type, ...)
if ESX then
xPlayer = ESX.GetPlayerFromId(source)
if type == "cash" then
xPlayer.addMoney(...)
else
xPlayer.addAccountMoney(type, ...)
end
else
xPlayer = QBCore.Functions.GetPlayer(source)
xPlayer.Functions.AddMoney(type, ...)
end
end
RemoveMoney = function(source, type, ...)
if ESX then
xPlayer = ESX.GetPlayerFromId(source)
if type == "cash" then
xPlayer.removeMoney(...)
else
xPlayer.removeAccountMoney(type, ...)
end
else
xPlayer = QBCore.Functions.GetPlayer(source)
xPlayer.Functions.RemoveMoney(type, ...)
end
end
HaveMoney = function(source, type, amount)
if ESX then
xPlayer = ESX.GetPlayerFromId(source)
if type == "cash" then
return xPlayer.getMoney(type) >= amount
else
return xPlayer.getAccount(type).money >= amount
end
else
xPlayer = QBCore.Functions.GetPlayer(source)
return xPlayer.Functions.GetMoney(type) >= amount
end
end
--// MySQL //--
StartMySQL = function()
MySQL = {
Async = {},
Sync = {},
}
local function safeParameters(params)
if nil == params then
return {[''] = ''}
end
assert(type(params) == "table", "A table is expected")
if next(params) == nil then
return {[''] = ''}
end
return params
end
function MySQL.Sync.execute(query, params)
assert(type(query) == "string" or type(query) == "number", "The SQL Query must be a string")
local res = 0
local finishedQuery = false
exports['mysql-async']:mysql_execute(query, safeParameters(params), function (result)
res = result
finishedQuery = true
end)
repeat Citizen.Wait(0) until finishedQuery == true
return res
end
function MySQL.Sync.fetchAll(query, params)
assert(type(query) == "string" or type(query) == "number", "The SQL Query must be a string")
local res = {}
local finishedQuery = false
exports['mysql-async']:mysql_fetch_all(query, safeParameters(params), function (result)
res = result
finishedQuery = true
end)
repeat Citizen.Wait(0) until finishedQuery == true
return res
end
function MySQL.Sync.fetchScalar(query, params)
assert(type(query) == "string" or type(query) == "number", "The SQL Query must be a string")
local res = ''
local finishedQuery = false
exports['mysql-async']:mysql_fetch_scalar(query, safeParameters(params), function (result)
res = result
finishedQuery = true
end)
repeat Citizen.Wait(0) until finishedQuery == true
return res
end
function MySQL.Sync.insert(query, params)
assert(type(query) == "string" or type(query) == "number", "The SQL Query must be a string")
local res = 0
local finishedQuery = false
exports['mysql-async']:mysql_insert(query, safeParameters(params), function (result)
res = result
finishedQuery = true
end)
repeat Citizen.Wait(0) until finishedQuery == true
return res
end
function MySQL.Sync.store(query)
assert(type(query) == "string", "The SQL Query must be a string")
local res = -1
local finishedQuery = false
exports['mysql-async']:mysql_store(query, function (result)
res = result
finishedQuery = true
end)
repeat Citizen.Wait(0) until finishedQuery == true
return res
end
function MySQL.Sync.transaction(querys, params)
local res = 0
local finishedQuery = false
exports['mysql-async']:mysql_transaction(querys, params, function (result)
res = result
finishedQuery = true
end)
repeat Citizen.Wait(0) until finishedQuery == true
return res
end
function MySQL.Async.execute(query, params, func)
assert(type(query) == "string" or type(query) == "number", "The SQL Query must be a string")
exports['mysql-async']:mysql_execute(query, safeParameters(params), func)
end
function MySQL.Async.fetchAll(query, params, func)
assert(type(query) == "string" or type(query) == "number", "The SQL Query must be a string")
exports['mysql-async']:mysql_fetch_all(query, safeParameters(params), func)
end
function MySQL.Async.fetchScalar(query, params, func)
assert(type(query) == "string" or type(query) == "number", "The SQL Query must be a string")
exports['mysql-async']:mysql_fetch_scalar(query, safeParameters(params), func)
end
function MySQL.Async.insert(query, params, func)
assert(type(query) == "string" or type(query) == "number", "The SQL Query must be a string")
exports['mysql-async']:mysql_insert(query, safeParameters(params), func)
end
function MySQL.Async.store(query, func)
assert(type(query) == "string", "The SQL Query must be a string")
exports['mysql-async']:mysql_store(query, func)
end
function MySQL.Async.transaction(querys, params, func)
return exports['mysql-async']:mysql_transaction(querys, params, func)
end
function MySQL.ready(callback)
Citizen.CreateThread(function ()
while GetResourceState('mysql-async') ~= 'started' do
Citizen.Wait(0)
end
while not exports['mysql-async']:is_ready() do
Citizen.Wait(0)
end
callback()
end)
end
end
ExecuteSql = function(query, params)
if MySQL == nil then
StartMySQL()
end
if string.find(query, "SELECT") then
return MySQL.Sync.fetchAll(query, params)
elseif string.find(query, "INSERT") or string.find(query, "UPDATE") then
MySQL.Sync.execute(query, params)
end
end
--// Society //--
-- Item
SocietyAddItem = function(society, item, amount)
if not string.find(society, "society_") then
society = "society_"..society
end
TriggerEvent('esx_addoninventory:getSharedInventory', society, function(deposit)
deposit.addItem(item, amount)
end)
end
SocietyRemoveItem = function(society, item, amount)
if not string.find(society, "society_") then
society = "society_"..society
end
TriggerEvent('esx_addoninventory:getSharedInventory', society, function(deposit)
deposit.removeItem(item, amount)
end)
end
SocietyGetItem = function(society, item)
if not string.find(society, "society_") then
society = "society_"..society
end
local _return = nil
TriggerEvent('esx_addoninventory:getSharedInventory', society, function(deposit)
_return = deposit.getItem(item)
end)
while _return == nil do
Citizen.Wait(1)
end
return _return
end
SocietyHaveItem = function(society, item)
if not string.find(society, "society_") then
society = "society_"..society
end
local _return = nil
TriggerEvent('esx_addoninventory:getSharedInventory', society, function(deposit)
local inventoryItem = deposit.getItem(item).count
_return = inventoryItem > 0
end)
while _return == nil do
Citizen.Wait(1)
end
return _return
end
SocietyHaveItemQuantity = function(society, item, quantity)
if not string.find(society, "society_") then
society = "society_"..society
end
local _return = nil
TriggerEvent('esx_addoninventory:getSharedInventory', society, function(deposit)
local inventoryItem = deposit.getItem(item).count
_return = inventoryItem > quantity
end)
while _return == nil do
Citizen.Wait(1)
end
return _return
end
-- Money
SocietyAddMoney = function(society, amount)
if not string.find(society, "society_") then
society = "society_"..society
end
TriggerEvent('esx_addonaccount:getSharedAccount', society, function(account)
account.addMoney(amount)
end)
end
SocietyRemoveMoney = function(society, amount)
if not string.find(society, "society_") then
society = "society_"..society
end
TriggerEvent('esx_addonaccount:getSharedAccount', society, function(account)
account.removeMoney(amount)
end)
end
SocietyHaveMoney = function(society, amount)
if not string.find(society, "society_") then
society = "society_"..society
end
local have = nil
TriggerEvent('esx_addonaccount:getSharedAccount', society, function(account)
have = account.money >= amount
end)
while have == nil do
Citizen.Wait(1)
end
return have
end
--// Misc //--
printd = function(_table, advanced)
if advanced then
local printTable_cache = {}
local function sub_printTable(t, indent)
if (printTable_cache[tostring(t)]) then
print(indent.."*"..tostring(t))
else
printTable_cache[tostring(t)] = true
if (type(t) == "table") then
for pos,val in pairs(t) do
if (type(val) == "table") then
print(indent.."["..pos.."] => "..tostring(t).. " {" )
sub_printTable(val, indent..string.rep(" ", string.len(pos)+8))
print(indent..string.rep(" ", string.len(pos)+6 ).."}")
elseif (type(val) == "string") then
print(indent.."["..pos.."] => \"" .. val .. "\"")
else
print(indent.."["..pos.."] => "..tostring(val))
end
end
else
print(indent..tostring(t))
end
end
end
if (type(_table) == "table") then
print(tostring(_table).." {")
sub_printTable(_table, " ")
print("}")
else
developer("^1Error^0", "error dumping table ".._table.." why isnt a table", "")
end
else
if type(_table) == "table" then
print(json.encode(_table, {indent = true}))
else
developer("^1Error^0", "error dumping table ".._table.." why isnt a table", "")
end
end
end
local string_gsub = string.gsub
string.multigsub = function(string, table, new)
if type(table) then
for i=1, #table do
string = string_gsub(string, table[i], new[i])
end
else
for i=1, #table do
string = string_gsub(string, table[i], new)
end
end
return string
end
table.fexist = function(_table, field)
return _table[field] ~= nil
end
local table_remove = table.remove
table.remove = function(_table, index, onlyfirst)
if type(index) == "number" then
return table_remove(_table, index)
elseif type(index) == "string" then
for k, v in pairs(_table) do
if k == index then
_table[k] = nil -- Can be bugged, probably in future update will be changed with a empty table
if onlyfirst then
return k
end
end
end
else
return table_remove(_table)
end
end
---Check if a table is empty.
---@param t table
---@return boolean
table.empty = function(t)
return next(t) == nil
end
---Internal usage: Inserts a value into a table at a given key, or appends to the end if the key is a number.
---@param t table
---@param k any
---@param v any
local table_insert = function(t, k, v)
if type(k) == "number" then
table.insert(t, v)
else
t[k] = v
end
end
---Merges two tables together, if the same key is found in both tables the second table takes precedence.
---@param t1 table
---@param t2 table
---@return table
table.merge = function(t1, t2)
---@type table
local result = table.clone(t1)
for k, v in pairs(t2) do
table_insert(result, k, v)
end
return result
end
---Checks if the given value exists in the table, if a function is given it test it on each value until it returns true.
---@param t table
---@param value any|fun(value: any): boolean
---@return boolean
table.includes = function(t, value)
if type(value) == "function" then
for _, v in pairs(t) do
if value(v) then
return true
end
end
else
for _, v in pairs(t) do
if value == v then
return true
end
end
end
return false
end
---Filters a table using a given filter, which can be an another table or a function.
---@param t table
---@param filter table|fun(k: any, v: any): boolean
---@return table
table.filter = function(t, filter)
local result = {}
if type(filter) == "function" then
-- returns true.
for k, v in pairs(t) do
if filter(k, v) then
table_insert(result, k, v)
end
end
elseif type(filter) == "table" then
for k, v in pairs(t) do
if table.includes(filter, v) then
table_insert(result, k, v)
end
end
end
return result
end
---Searches a table for the given value and returns the key and value if found.
---@param t table
---@param value any|fun(value: any): boolean
---@return any
table.find = function(t, value)
if type(value) == "function" then
for k, v in pairs(t) do
if value(v) then
return k, v
end
end
else
for k, v in pairs(t) do
if value == v then
return k, v
end
end
end
end
---Returns a table with all keys of the given table.
---@param t table
---@return table
table.keys = function(t)
local keys = {}
for k, _ in pairs(t) do
table.insert(keys, k)
end
return keys
end
---Returns a table with all values of the given table.
---@param t table
---@return table
table.values = function(t)
local values = {}
for _, v in pairs(t) do
table.insert(values, v)
end
return values
end
math.round = function(number, decimals)
local _ = 10 ^ decimals
return math.floor((number * _) + 0.5) / (_)
end
-- https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/
math.lerp = function(start, _end, perc)
return start + (_end - start) * perc
end
math.invlerp = function(start, _end, value)
return (value - start) / (_end - start)
end
GetDataForJob = function(job)
return exports["utility_lib"]:GetDataForJob(job)
end
quat2euler = function(q)
-- roll (x-axis rotation)
local sinr_cosp = 2 * (q.w * q.x + q.y * q.z);
local cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y);
local roll = math.atan2(sinr_cosp, cosr_cosp);
-- pitch (y-axis rotation)
local sinp = math.sqrt(1 + 2 * (q.w * q.y - q.x * q.z));
local cosp = math.sqrt(1 - 2 * (q.w * q.y - q.x * q.z));
local pitch = 2 * math.atan2(sinp, cosp) - math.pi / 2;
-- yaw (z-axis rotation)
local siny_cosp = 2 * (q.w * q.z + q.x * q.y);
local cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z);
local yaw = math.atan2(siny_cosp, cosy_cosp);
return vec3(math.deg(roll), math.deg(pitch), math.deg(yaw));
end
GenerateMatrix = function(pos, rot)
local rx, ry, rz = math.rad(rot.x), math.rad(rot.y), math.rad(rot.z)
-- Precompute
local cosX, sinX = math.cos(rx), math.sin(rx)
local cosY, sinY = math.cos(ry), math.sin(ry)
local cosZ, sinZ = math.cos(rz), math.sin(rz)
local mrx = mat3(
vec3(1, 0, 0),
vec3(0, cosX, -sinX),
vec3(0, sinX, cosX)
)
local mry = mat3(
vec3(cosY, 0, sinY),
vec3(0, 1, 0),
vec3(-sinY, 0, cosY)
)
local mrz = mat3(
vec3(cosZ, -sinZ, 0),
vec3(sinZ, cosZ, 0),
vec3(0, 0, 1)
)
local rotationMatrix = mrx * mry * mrz
-- Construct the final transform matrix
local transformMatrix = mat4(
vec4(rotationMatrix[1].x, rotationMatrix[2].x, rotationMatrix[3].x, 0),
vec4(rotationMatrix[1].y, rotationMatrix[2].y, rotationMatrix[3].y, 0),
vec4(rotationMatrix[1].z, rotationMatrix[2].z, rotationMatrix[3].z, 0),
vec4(pos.x, pos.y, pos.z, 1)
)
return transformMatrix
end
GetOffsetFromPositionInWorldCoords = function(pos, rot, offset)
local m = GenerateMatrix(pos, rot)
return m * offset
end
--// Slices //--
local sliceSize = 100.0
local slicesLength = 8100
local sliceCollumns = slicesLength / sliceSize
function GetSliceColRowFromCoords(coords)
local row = math.floor((coords.x) / sliceSize)
local col = math.floor((coords.y) / sliceSize)
return col, row
end
function GetWorldCoordsFromSlice(slice)
local col = math.floor(slice / sliceCollumns)
local row = slice % sliceCollumns
return vec3((row * sliceSize), (col * sliceSize), 0.0)
end
function GetSliceIdFromColRow(col, row)
return math.floor(col * sliceCollumns + row)
end
function GetSliceFromCoords(pos)
local col, row = GetSliceColRowFromCoords(pos)
return GetSliceIdFromColRow(col, row)
end
function GetSurroundingSlices(slice)
local top = slice - sliceCollumns
local bottom = slice + sliceCollumns
local right = slice - 1
local left = slice + 1
local topright = slice - sliceCollumns - 1
local topleft = slice - sliceCollumns + 1
local bottomright = slice + sliceCollumns - 1
local bottomleft = slice + sliceCollumns + 1
return {top, bottom, left, right, topright, topleft, bottomright, bottomleft}
end
--// UtilityNet //--
local CreatedEntities = {}
UtilityNet = {}
UtilityNet.ForEachEntity = function(fn, slice)
if slice then
if not GlobalState.Entities[slice] then
return
end
for k,v in pairs(GlobalState.Entities[slice]) do
local ret = fn(v, k)
if ret ~= nil then
return ret
end
end
else
if not GlobalState.Entities then
return
end
for sliceI,slice in pairs(GlobalState.Entities) do
for k2, v in pairs(slice) do
local ret = fn(v, k2)
if ret ~= nil then
return ret
end
end
end
end
end
UtilityNet.CreateEntity = function(model, coords, options)
local id = exports["utility_lib"]:CreateEntity(model, coords, options)
table.insert(CreatedEntities, id)
return id
end
UtilityNet.SetEntityModel = function(uNetId, model)
return exports["utility_lib"]:SetEntityModel(uNetId, model)
end
UtilityNet.DeleteEntity = function(uNetId)
for k, v in pairs(CreatedEntities) do
if v == uNetId then
table.remove(CreatedEntities, k)
break
end
end
return exports["utility_lib"]:DeleteEntity(uNetId)
end
-- Returns the slice the entity is in
UtilityNet.InternalFindFromNetId = function(uNetId)
for sliceI, slice in pairs(GlobalState.Entities) do
if slice[uNetId] then
return slice[uNetId], sliceI
end
end
end
UtilityNet.DoesUNetIdExist = function(uNetId)
local entity = UtilityNet.InternalFindFromNetId(uNetId)
return entity or false
end
UtilityNet.GetEntityCoords = function(uNetId)
local entity = UtilityNet.InternalFindFromNetId(uNetId)
if entity then
return entity.coords
end
end
UtilityNet.GetEntityRotation = function(uNetId)
local entity = UtilityNet.InternalFindFromNetId(uNetId)
if entity then
return entity.options.rotation
end
end
UtilityNet.GetEntityModel = function(uNetId)
local entity = UtilityNet.InternalFindFromNetId(uNetId)
if entity then
return entity.model
end
end
UtilityNet.SetModelRenderDistance = function(model, distance)
return exports["utility_lib"]:SetModelRenderDistance(model, distance)
end
UtilityNet.SetEntityCoords = function(uNetId, newCoords)
return exports["utility_lib"]:SetEntityCoords(uNetId, newCoords)
end
UtilityNet.SetEntityRotation = function(uNetId, newRotation)
return exports["utility_lib"]:SetEntityRotation(uNetId, newRotation)
end
UtilityNet.DetachEntity = function(uNetId)
TriggerEvent("Utility:Net:DetachEntity", uNetId)
end
local getValueAsStateTable = nil
getValueAsStateTable = function(id, baseKey, depth)
depth = depth or {}
local getCurrentTable = function()
local baseTable = exports["utility_lib"]:GetEntityStateValue(id, baseKey)
-- Dive into table
for k,v in pairs(depth) do
baseTable = baseTable[v]
end
return baseTable
end
return setmetatable({
__internal_statetable = true,
raw = function(self)
return getCurrentTable()
end
}, {
-- Iterators
__pairs = function(self)
return pairs(getCurrentTable())
end,
__ipairs = function(self)
return ipairs(getCurrentTable())
end,
__len = function(self)
return #getCurrentTable()
end,
__index = function(_, k)
local currentTable = getCurrentTable()
if type(currentTable[k]) == "table" then
-- Clone the table to dont mess up the current depth table
local clonedDepth = table.clone(depth)
table.insert(clonedDepth, k)
-- Generate another state table but more in depth
return getValueAsStateTable(id, baseKey, clonedDepth)
else
return currentTable[k]
end
end,
__newindex = function(_, k, v)
local baseTable = exports["utility_lib"]:GetEntityStateValue(id, baseKey)
local currentTable = baseTable
-- Dive into table
for k,v in pairs(depth) do
currentTable = currentTable[v]
end
-- Set
currentTable[k] = v
-- Update state table
exports["utility_lib"]:SetEntityStateValue(id, baseKey, baseTable)
end
})
end
UtilityNet.State = function(id)
local state = setmetatable({
raw = function(self)
return exports["utility_lib"]:GetEntityStateValue(id)
end
}, {
__index = function(_, k)
local value = exports["utility_lib"]:GetEntityStateValue(id, k)
if type(value) == "table" then
return getValueAsStateTable(id, k, {})
else
return value
end
end,
__newindex = function(_, k, v)
-- If the value is a state table get the raw table (without metatable)
if type(v) == "table" and v.__internal_statetable then
v = v:raw()
end
exports["utility_lib"]:SetEntityStateValue(id, k, v)
end
})
return state
end
AddEventHandler("onResourceStop", function(resourceName)
if resourceName == GetCurrentResourceName() then
for k, v in pairs(CreatedEntities) do
exports["utility_lib"]:DeleteEntity(v)
end
end
end)