mirror of
https://github.com/esx-framework/esx_core.git
synced 2026-08-28 17:01:14 +00:00
Merge remote-tracking branch 'Stane/esx_lib'
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
--All client-side functions outsourced from the Core to the lib will be stored here for compatability, e.g:
|
||||
|
||||
ESX.Streaming.RequestModel = xLib.streaming.requestModel
|
||||
ESX.Streaming.RequestStreamedTextureDict = xLib.streaming.requestStreamedTextureDict
|
||||
ESX.Streaming.RequestNamedPtfxAsset = xLib.streaming.requestNamedPtfxAsset
|
||||
ESX.Streaming.RequestAnimSet = xLib.streaming.requestAnimSet
|
||||
ESX.Streaming.RequestAnimDict = xLib.streaming.requestAnimDict
|
||||
ESX.Streaming.RequestWeaponAsset = xLib.streaming.requestWeaponAsset
|
||||
@@ -15,6 +15,7 @@ shared_scripts {
|
||||
'shared/main.lua',
|
||||
'shared/functions.lua',
|
||||
'shared/modules/*.lua',
|
||||
'shared/compat.lua'
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
@@ -39,6 +40,8 @@ server_scripts {
|
||||
'server/modules/createJob.lua',
|
||||
'server/migration/**/main.lua',
|
||||
'server/migration/main.lua',
|
||||
|
||||
'server/compat.lua'
|
||||
}
|
||||
|
||||
client_scripts {
|
||||
@@ -57,6 +60,8 @@ client_scripts {
|
||||
'client/modules/interactions.lua',
|
||||
'client/modules/scaleform.lua',
|
||||
'client/modules/streaming.lua',
|
||||
|
||||
'shared/compat.lua'
|
||||
}
|
||||
|
||||
ui_page {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
--[[ All server-side functions outsourced from the Core to the lib will be stored here for compatability, e.g:
|
||||
--]]
|
||||
@@ -0,0 +1,5 @@
|
||||
--All shared functions outsourced from the Core to the lib will be stored here for compatability, e.g:
|
||||
|
||||
ESX.SetTimeout = xLib.timeout.setTimeout
|
||||
ESX.ClearTimeout = xLib.timeout.clearTimeout
|
||||
ESX.Await = xLib.waitFor
|
||||
@@ -178,45 +178,6 @@ function ESX.IsFunctionReference(val)
|
||||
return typeVal == "function" or (typeVal == "table" and type(getmetatable(val)?.__call) == "function")
|
||||
end
|
||||
|
||||
---@param conditionFunc function A function that is repeatedly called until it returns a truthy value or the timeout is exceeded.
|
||||
---@param errorMessage? string Optional. If set, an error will be thrown with this message if the condition is not met within the timeout. If not set, no error will be thrown.
|
||||
---@param timeoutMs? number Optional. The maximum time to wait (in milliseconds) for the condition to be met. Defaults to 1000ms.
|
||||
---@return boolean, any: Returns success status and the returned value of the condition function.
|
||||
function ESX.Await(conditionFunc, errorMessage, timeoutMs)
|
||||
timeoutMs = timeoutMs or 1000
|
||||
|
||||
if timeoutMs < 0 then
|
||||
error("Timeout should be a positive number.")
|
||||
end
|
||||
|
||||
if not ESX.IsFunctionReference(conditionFunc) then
|
||||
error("Condition Function should be a function reference.")
|
||||
end
|
||||
|
||||
-- since errorMessage is optional, we only validate it if the user provided it.
|
||||
if errorMessage then
|
||||
ESX.AssertType(errorMessage, "string", "errorMessage should be a string.")
|
||||
end
|
||||
|
||||
local invokingResource = GetInvokingResource()
|
||||
local startTimeMs = GetGameTimer()
|
||||
while GetGameTimer() - startTimeMs < timeoutMs do
|
||||
local result = conditionFunc()
|
||||
|
||||
if result then
|
||||
return true, result
|
||||
end
|
||||
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
if errorMessage then
|
||||
error(("[%s] -> %s"):format(invokingResource, errorMessage))
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---@param str string
|
||||
---@param allowDigits boolean? Allow numbers if necessary
|
||||
---@return boolean
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
|
||||
use_experimental_fxv2_oal 'yes'
|
||||
lua54 'yes'
|
||||
|
||||
author 'ESX Team'
|
||||
version '0.01'
|
||||
description 'Official ESX library'
|
||||
|
||||
files {
|
||||
'imports.lua',
|
||||
'imports/**/client.lua',
|
||||
'imports/**/shared.lua',
|
||||
}
|
||||
|
||||
shared_scripts {
|
||||
'resource/init.lua',
|
||||
'resource/**/shared.lua',
|
||||
}
|
||||
|
||||
client_scripts {
|
||||
'resource/**/client.lua',
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
'resource/**/server.lua',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
--! DISCLAIMER
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
-- Const Variables
|
||||
local CONTEXT<const> = IsDuplicityVersion() and 'server' or 'client'
|
||||
local LIB_NAME <const> = 'esx_lib'
|
||||
local IS_DEBUG <const> = GetConvar('xLib:debug', 'false') == 'true'
|
||||
|
||||
|
||||
---@alias Module table | function
|
||||
---@class xLib
|
||||
---@field name string
|
||||
---@field side 'server' | 'client'
|
||||
|
||||
-------------------------------------------------
|
||||
--- Core functions for modules
|
||||
-------------------------------------------------
|
||||
|
||||
--- Function that returns nothing
|
||||
--- Used as a "shortcut"
|
||||
function Noop() end
|
||||
|
||||
---Loads a module into memory
|
||||
---@param self xLib
|
||||
---@param name string -- module name
|
||||
---@return Module | nil
|
||||
local function loadModule(self, name)
|
||||
local directory = ('imports/%s'):format(name)
|
||||
local chunk = LoadResourceFile(LIB_NAME, ('%s/%s.lua'):format(directory, CONTEXT))
|
||||
local shared_chunk = LoadResourceFile(LIB_NAME, ('%s/shared.lua'):format(directory))
|
||||
|
||||
if shared_chunk then
|
||||
chunk = chunk and ('%s\n%s'):format(shared_chunk, chunk) or shared_chunk
|
||||
end
|
||||
|
||||
if not chunk then
|
||||
return
|
||||
end
|
||||
|
||||
-- Second argument is a chunk name
|
||||
local fn, err = load(chunk, ('@@%s/imports/%s/%s.lua'):format(LIB_NAME, name, CONTEXT))
|
||||
|
||||
if not fn or err then
|
||||
if shared_chunk then
|
||||
print(('[%s] Found an error while importing - %s! Try updating %s to the latest version!'):format(LIB_NAME,
|
||||
name, LIB_NAME))
|
||||
|
||||
-- Tries to load only shared.lua
|
||||
fn, err = load(shared_chunk, ('@@%s/imports/%s/%s.lua'):format(LIB_NAME, name, 'shared'))
|
||||
end
|
||||
|
||||
if not fn or err then
|
||||
return error(('Completly failed importing module - %s; error - %s'):format(name, err))
|
||||
end
|
||||
end
|
||||
|
||||
local result = fn()
|
||||
self[name] = result or Noop
|
||||
|
||||
return self[name]
|
||||
end
|
||||
|
||||
---Function that is responsible for lazy loading
|
||||
---@param self xLib
|
||||
---@param index string -- name of the function/key that is called
|
||||
---@param ... unknown -- function params
|
||||
---@return Module
|
||||
local function call(self, index, ...)
|
||||
local module = rawget(self, index) -- uses rawget not to trigger any metamethods
|
||||
|
||||
-- Module Loading
|
||||
if not module then
|
||||
self[index] = Noop -- to prevent module from loading again if doesn't exists.
|
||||
|
||||
module = loadModule(self, index)
|
||||
|
||||
if not module then
|
||||
local function method(...)
|
||||
return exports[LIB_NAME][index](nil, ...)
|
||||
end
|
||||
|
||||
if not ... then
|
||||
self[index] = method
|
||||
end
|
||||
|
||||
return method
|
||||
end
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
--- Environment Setup
|
||||
-------------------------------------------------
|
||||
|
||||
-- Creation of xLib object
|
||||
---@diagnostic disable-next-line: lowercase-global
|
||||
local xLib = setmetatable({
|
||||
name = LIB_NAME,
|
||||
side = CONTEXT,
|
||||
debug = IS_DEBUG
|
||||
}, {
|
||||
-- Lazy Loading - loads module only when accessed
|
||||
__index = call,
|
||||
__call = call,
|
||||
})
|
||||
|
||||
-- Allows to xLib to be accessible in resource that imports a lib
|
||||
_ENV.xLib = xLib
|
||||
_ENV.require = xLib.require
|
||||
@@ -0,0 +1,148 @@
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
local pendingCallbacks = {}
|
||||
local timers = {}
|
||||
local cbEvent = '__xLib_cb_%s'
|
||||
local callbackTimeout = GetConvarInt('xLib:callbackTimeout', 300000)
|
||||
local resource_name = GetCurrentResourceName() --TODO: Add cache
|
||||
|
||||
RegisterNetEvent(cbEvent:format(resource_name), function(key, ...)
|
||||
if source == '' then return end
|
||||
|
||||
local cb = pendingCallbacks[key]
|
||||
|
||||
if not cb then return end
|
||||
|
||||
pendingCallbacks[key] = nil
|
||||
|
||||
cb(...)
|
||||
end)
|
||||
|
||||
---@param event string
|
||||
---@param delay? number | false prevent the event from being called for the given time
|
||||
local function eventTimer(event, delay)
|
||||
if xLib.verify(delay, 'number') then
|
||||
if delay > 0 then
|
||||
local time = GetGameTimer()
|
||||
|
||||
if (timers[event] or 0) > time then
|
||||
return false
|
||||
end
|
||||
|
||||
timers[event] = time + delay
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@param _ any
|
||||
---@param event string
|
||||
---@param delay number | false | nil
|
||||
---@param cb function | false
|
||||
---@param ... any
|
||||
---@return ...
|
||||
local function triggerServerCallback(_, event, delay, cb, ...)
|
||||
if not eventTimer(event, delay) then return end
|
||||
|
||||
local key
|
||||
|
||||
repeat
|
||||
key = ('%s:%s'):format(event, math.random(0, 100000))
|
||||
until not pendingCallbacks[key]
|
||||
|
||||
TriggerServerEvent('xLib:validateCallback', event, resource_name, key)
|
||||
TriggerServerEvent(cbEvent:format(event), resource_name, key, ...)
|
||||
|
||||
---@type promise | false
|
||||
local promise = not cb and promise.new()
|
||||
|
||||
pendingCallbacks[key] = function(response, ...)
|
||||
if response == 'cb_invalid' then
|
||||
response = ("callback '%s' does not exist"):format(event)
|
||||
|
||||
return promise and promise:reject(response) or error(response)
|
||||
end
|
||||
|
||||
response = { response, ... }
|
||||
|
||||
if promise then
|
||||
return promise:resolve(response)
|
||||
end
|
||||
|
||||
if cb then
|
||||
cb(table.unpack(response))
|
||||
end
|
||||
end
|
||||
|
||||
if promise then
|
||||
SetTimeout(callbackTimeout, function() promise:reject(("callback event '%s' timed out"):format(key)) end)
|
||||
|
||||
return table.unpack(Citizen.Await(promise))
|
||||
end
|
||||
end
|
||||
|
||||
---@overload fun(event: string, delay: number | false, cb: function, ...)
|
||||
xLib.callback = setmetatable({}, {
|
||||
__call = function(_, event, delay, cb, ...)
|
||||
if not cb then
|
||||
warn(("callback event '%s' does not have a function to callback to and will instead await\nuse xLib.callback.await or a regular event to remove this warning")
|
||||
:format(event))
|
||||
else
|
||||
local cbType = type(cb)
|
||||
|
||||
if cbType == 'table' and getmetatable(cb)?.__call then
|
||||
cbType = 'function'
|
||||
end
|
||||
|
||||
xLib.verify(cbType, 'function', true)
|
||||
end
|
||||
|
||||
return triggerServerCallback(_, event, delay, cb, ...)
|
||||
end
|
||||
})
|
||||
|
||||
---@param event string
|
||||
---@param delay? number | false prevent the event from being called for the given time.
|
||||
---Sends an event to the server and halts the current thread until a response is returned.
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function xLib.callback.await(event, delay, ...)
|
||||
return triggerServerCallback(nil, event, delay, false, ...)
|
||||
end
|
||||
|
||||
local function callbackResponse(success, result, ...)
|
||||
if not success then
|
||||
if result then
|
||||
return print(('^1SCRIPT ERROR: %s^0\n%s'):format(result,
|
||||
Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()) or ''))
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return result, ...
|
||||
end
|
||||
|
||||
local pcall = pcall
|
||||
|
||||
---@param name string
|
||||
---@param cb function
|
||||
---Registers an event handler and callback function to respond to server requests.
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function xLib.callback.register(name, cb)
|
||||
local event = cbEvent:format(name)
|
||||
|
||||
xLib.setValidCallback(name, true)
|
||||
|
||||
RegisterNetEvent(event, function(resource, key, ...)
|
||||
TriggerServerEvent(cbEvent:format(resource), key, callbackResponse(pcall(cb, ...)))
|
||||
end)
|
||||
end
|
||||
|
||||
return xLib.callback
|
||||
@@ -0,0 +1,127 @@
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
local pendingCallbacks = {}
|
||||
local cbEvent = '__xLib_cb_%s'
|
||||
local callbackTimeout = GetConvarInt('xLib:callbackTimeout', 300000)
|
||||
local resource_name = GetCurrentResourceName() --TODO: Add cache
|
||||
|
||||
RegisterNetEvent(cbEvent:format(resource_name), function(key, ...)
|
||||
local cb = pendingCallbacks[key]
|
||||
|
||||
if not cb then return end
|
||||
|
||||
pendingCallbacks[key] = nil
|
||||
|
||||
cb(...)
|
||||
end)
|
||||
|
||||
---@param _ any
|
||||
---@param event string
|
||||
---@param playerId number
|
||||
---@param cb function|false
|
||||
---@param ... any
|
||||
---@return ...
|
||||
local function triggerClientCallback(_, event, playerId, cb, ...)
|
||||
xLib.verify(playerId, 'playerId', true)
|
||||
|
||||
local key
|
||||
|
||||
repeat
|
||||
key = ('%s:%s:%s'):format(event, math.random(0, 100000), playerId)
|
||||
until not pendingCallbacks[key]
|
||||
|
||||
TriggerClientEvent('xLib:validateCallback', playerId, event, resource_name, key)
|
||||
TriggerClientEvent(cbEvent:format(event), playerId, resource_name, key, ...)
|
||||
|
||||
---@type promise | false
|
||||
local promise = not cb and promise.new()
|
||||
|
||||
pendingCallbacks[key] = function(response, ...)
|
||||
if response == 'cb_invalid' then
|
||||
response = ("callback '%s' does not exist"):format(event)
|
||||
|
||||
return promise and promise:reject(response) or error(response)
|
||||
end
|
||||
|
||||
response = { response, ... }
|
||||
|
||||
if promise then
|
||||
return promise:resolve(response)
|
||||
end
|
||||
|
||||
if cb then
|
||||
cb(table.unpack(response))
|
||||
end
|
||||
end
|
||||
|
||||
if promise then
|
||||
SetTimeout(callbackTimeout, function() promise:reject(("callback event '%s' timed out"):format(key)) end)
|
||||
|
||||
return table.unpack(Citizen.Await(promise))
|
||||
end
|
||||
end
|
||||
|
||||
---@overload fun(event: string, playerId: number, cb: function, ...)
|
||||
xLib.callback = setmetatable({}, {
|
||||
__call = function(_, event, playerId, cb, ...)
|
||||
if not cb then
|
||||
warn(("callback event '%s' does not have a function to callback to and will instead await\nuse xLib.callback.await or a regular event to remove this warning")
|
||||
:format(event))
|
||||
else
|
||||
local cbType = type(cb)
|
||||
|
||||
if cbType == 'table' and getmetatable(cb)?.__call then
|
||||
cbType = 'function'
|
||||
end
|
||||
|
||||
xLib.verify(cbType, 'function', true)
|
||||
end
|
||||
|
||||
return triggerClientCallback(_, event, playerId, cb, ...)
|
||||
end
|
||||
})
|
||||
|
||||
---@param event string
|
||||
---@param playerId number
|
||||
--- Sends an event to a client and halts the current thread until a response is returned.
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function xLib.callback.await(event, playerId, ...)
|
||||
return triggerClientCallback(nil, event, playerId, false, ...)
|
||||
end
|
||||
|
||||
local function callbackResponse(success, result, ...)
|
||||
if not success then
|
||||
if result then
|
||||
return print(('^1SCRIPT ERROR: %s^0\n%s'):format(result,
|
||||
Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()) or ''))
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return result, ...
|
||||
end
|
||||
|
||||
local pcall = pcall
|
||||
|
||||
---@param name string
|
||||
---@param cb function
|
||||
---Registers an event handler and callback function to respond to client requests.
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function xLib.callback.register(name, cb)
|
||||
event = cbEvent:format(name)
|
||||
|
||||
xLib.setValidCallback(name, true)
|
||||
|
||||
RegisterNetEvent(event, function(resource, key, ...)
|
||||
TriggerClientEvent(cbEvent:format(resource), source, key, callbackResponse(pcall(cb, source, ...)))
|
||||
end)
|
||||
end
|
||||
|
||||
return xLib.callback
|
||||
@@ -0,0 +1,35 @@
|
||||
---@class xClass
|
||||
---@field new function
|
||||
---@field constructor function?
|
||||
|
||||
---Create or register a class
|
||||
---@param copy? table
|
||||
---@return table
|
||||
function xLib.class(copy)
|
||||
xLib.verify(copy, { 'table', 'nil' }, true)
|
||||
|
||||
local class = copy and xLib.table.deepcopy(copy) or {}
|
||||
|
||||
class.__index = class
|
||||
class._isClass = true
|
||||
|
||||
setmetatable(class, {
|
||||
__newindex = function (_, k,v)
|
||||
rawset(class, k, v)
|
||||
end
|
||||
})
|
||||
|
||||
function class:new(...)
|
||||
local obj = setmetatable({}, class)
|
||||
|
||||
if xLib.verify(obj.constructor, 'function') then
|
||||
obj:constructor(...)
|
||||
end
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return class
|
||||
end
|
||||
|
||||
return xLib.class
|
||||
@@ -0,0 +1,10 @@
|
||||
xLib.colors = {}
|
||||
|
||||
xLib.colors.brand = GetConvar("esx:brand-color", "#FB9B04")
|
||||
xLib.colors.darkest = GetConvar("esx:darkest-color", "#161616")
|
||||
xLib.colors.dark = GetConvar("esx:dark-color", "#252525")
|
||||
xLib.colors.mid = GetConvar("esx:mid-color", "#383838")
|
||||
xLib.colors.light = GetConvar("esx:light-color", "#969696")
|
||||
xLib.colors.lightest = GetConvar("esx:lightest-color", "#F2F2F2")
|
||||
|
||||
return xLib.colors
|
||||
@@ -0,0 +1,66 @@
|
||||
local overloads <const> = {}
|
||||
|
||||
---@param name string
|
||||
---@param ... unknown
|
||||
local function getOverloadFunction(name, ...)
|
||||
local params = {...}
|
||||
local params_count = #params
|
||||
local is_valid, overload, correct_type
|
||||
|
||||
|
||||
for i=1, #overloads[name] do
|
||||
overload = overloads[name][i]
|
||||
|
||||
if not overload then
|
||||
goto continue
|
||||
end
|
||||
|
||||
is_valid = #overload.valid_types == params_count
|
||||
|
||||
if is_valid then
|
||||
for j = 1, #overload.valid_types do
|
||||
correct_type = overload.valid_types[j]
|
||||
if not xLib.verify(params[j], correct_type) then
|
||||
is_valid = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if is_valid then
|
||||
return overload.cb(...)
|
||||
end
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
error('[xLib] Couldn\'t find a correct method to overload')
|
||||
end
|
||||
|
||||
---Overloads a function
|
||||
---@param name string
|
||||
---@param valid_types CustomType | CustomType[]
|
||||
---@param cb function
|
||||
---@param obj? table
|
||||
function xLib.overload(name, valid_types, cb, obj)
|
||||
xLib.verify(name, {'string'}, true)
|
||||
|
||||
if not overloads[name] then
|
||||
overloads[name] = {}
|
||||
end
|
||||
|
||||
overloads[name][#overloads[name] + 1] = {
|
||||
valid_types = valid_types,
|
||||
cb = cb
|
||||
}
|
||||
|
||||
local env = obj and obj or _ENV
|
||||
|
||||
if not env[name] then
|
||||
env[name] = function(...)
|
||||
return getOverloadFunction(name, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return xLib.overload
|
||||
@@ -0,0 +1,200 @@
|
||||
--!DISCLAIMER
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
local LOADED <const> = {}
|
||||
local TEMP_DATA <const> = {}
|
||||
|
||||
local _require = require --original lua function
|
||||
|
||||
package = {
|
||||
path = './?.lua;./?/init.lua',
|
||||
preload = {},
|
||||
loaded = setmetatable({}, {
|
||||
__index = LOADED,
|
||||
__newindex = Noop,
|
||||
__metatable = false,
|
||||
})
|
||||
}
|
||||
|
||||
---Gets the resource name and the module's relative path
|
||||
---@param name string -- module name
|
||||
---@return string, string
|
||||
local function getModuleInfo(name)
|
||||
local resource = name:match('^@(.-)/.+') -- if name is for example @esx_lib/imports/require/initl.lua it will capture ESX_LIB
|
||||
|
||||
if resource then
|
||||
return resource, name:sub(#resource + 3) -- returns the path without script name
|
||||
end
|
||||
|
||||
local idx = 4-- call stack depth (kept slightly lower than expected depth "just in case")
|
||||
--- When indexing source of 4 it returns @@esx_lib/imports/require
|
||||
--- When indexing source of 5 it returns for example @@esx_test/client.lua
|
||||
--- Used for identifing resource name.
|
||||
|
||||
while true do
|
||||
local src = debug.getinfo(idx, 'S')?.source
|
||||
|
||||
if not src then
|
||||
error(('Couldn\'t find a source for "%s"'):format(name))
|
||||
end
|
||||
|
||||
resource = src:match('^@@([^/]+)/.+') -- returns resource name
|
||||
|
||||
if resource and not src:find('^@@esx_lib/imports/require') then
|
||||
return resource, name
|
||||
end
|
||||
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
---Searcher for a accessable path
|
||||
---@param name string
|
||||
---@param path string
|
||||
---@return string?, string? -- filename, error message
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function package.searchpath(name, path)
|
||||
local resource, module_name = getModuleInfo(name:gsub('%.', '/'))
|
||||
|
||||
local tried <const> = {}
|
||||
|
||||
for template in path:gmatch('[^;]+') do
|
||||
local file_name = template:gsub('^%./', ''):gsub('?', module_name:gsub('%.', '/') or module_name)
|
||||
|
||||
local file = LoadResourceFile(resource, file_name)
|
||||
|
||||
if file then
|
||||
TEMP_DATA[1] = file
|
||||
TEMP_DATA[2] = resource
|
||||
|
||||
return file_name
|
||||
end
|
||||
|
||||
tried[#tried+1] = ('no file "@%s/%s"'):format(resource, file_name)
|
||||
end
|
||||
|
||||
return nil, table.concat(tried, '\n\t')
|
||||
end
|
||||
|
||||
---Loads module
|
||||
---@param name string
|
||||
---@param env? unknown
|
||||
---@return function?, string?
|
||||
local function loadModule(name, env)
|
||||
local file_name, err = package.searchpath(name, package.path)
|
||||
|
||||
if file_name then
|
||||
local file = TEMP_DATA[1]
|
||||
local resource = TEMP_DATA[2]
|
||||
|
||||
table.wipe(TEMP_DATA)
|
||||
|
||||
return assert(load(file, ('@@%s/%s'):format(resource, file_name), 't', env or _ENV))
|
||||
end
|
||||
|
||||
return nil, err or 'unknown error'
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-doc-alias
|
||||
---@alias PackageSearcher
|
||||
---| fun(name: string): function loader
|
||||
---| fun(name: string): nil|false , string error message
|
||||
---| fun(name: string): function?, string?
|
||||
|
||||
---@type PackageSearcher[]
|
||||
package.searchers = {
|
||||
function (name)
|
||||
local ok, result = pcall(_require, name)
|
||||
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
|
||||
return ok, result
|
||||
end,
|
||||
function (name)
|
||||
if package.preload[name] ~= nil then
|
||||
return package.preload[name]
|
||||
end
|
||||
|
||||
return nil, ('no field package.preload["%s"]'):format(name)
|
||||
end,
|
||||
function(name)
|
||||
return loadModule(name)
|
||||
end,
|
||||
}
|
||||
|
||||
---Loads and runs a Lua file at the given path. Unlike require, the chunk is not cached for future use.
|
||||
---@param file_path string
|
||||
---@param env? unknown
|
||||
function xLib.load(file_path, env)
|
||||
xLib.verify(file_path, 'string', true)
|
||||
|
||||
local result, err = loadModule(file_path, env)
|
||||
|
||||
if result then
|
||||
return result()
|
||||
end
|
||||
|
||||
error(('file "%s" not found\n\t%s'):format(file_path, err))
|
||||
end
|
||||
|
||||
---Loads and decodes a json file at the given path.
|
||||
---@param file_path string
|
||||
function xLib.loadJson(file_path)
|
||||
xLib.verify(file_path, 'string', true)
|
||||
|
||||
local res_source, mod_path = getModuleInfo(file_path:gsub('%.', '/'))
|
||||
|
||||
local res_file = LoadResourceFile(res_source, ('%s.json'):format(mod_path))
|
||||
|
||||
if res_file then
|
||||
return json.decode(res_file)
|
||||
end
|
||||
|
||||
error(('json file "%s" not found\n\tno file "%s/%s.json"'):format(file_path, res_source, mod_path))
|
||||
end
|
||||
|
||||
---Loads the given module, returns any value returned by the seacher (`true` when `nil`).\
|
||||
---Passing `@resourceName.modName` loads a module from a remote resource.
|
||||
---@param name string
|
||||
---@return unknown
|
||||
function xLib.require(name)
|
||||
xLib.verify(name, 'string', true)
|
||||
|
||||
local module = LOADED[name]
|
||||
|
||||
if module == 'loading' then
|
||||
error(('^1circular-dependency occurred when loading module "%s"^0'):format(name))
|
||||
end
|
||||
|
||||
if module ~= nil then return module end
|
||||
|
||||
LOADED[name] = 'loading'
|
||||
|
||||
local err = {}
|
||||
|
||||
for i = 1, #package.searchers do
|
||||
local result, err_msg = package.searchers[i](name)
|
||||
|
||||
if result then
|
||||
if type(result) == 'function' then result = result() end
|
||||
LOADED[name] = result or result == nil
|
||||
|
||||
return LOADED[name]
|
||||
end
|
||||
|
||||
|
||||
err[#err + 1] = err_msg
|
||||
end
|
||||
|
||||
error(('%s'):format(table.concat(err, '\n\t')))
|
||||
end
|
||||
|
||||
return xLib.require
|
||||
+21
-7
@@ -1,9 +1,9 @@
|
||||
ESX.Streaming = {}
|
||||
xLib.streaming = {}
|
||||
|
||||
---@param modelHash number | string
|
||||
---@param cb? function
|
||||
---@return number | nil
|
||||
function ESX.Streaming.RequestModel(modelHash, cb)
|
||||
xLib.streaming.requestModel = function(modelHash, cb)
|
||||
modelHash = type(modelHash) == "number" and modelHash or joaat(modelHash)
|
||||
|
||||
if not IsModelInCdimage(modelHash) then return end
|
||||
@@ -17,7 +17,7 @@ end
|
||||
---@param textureDict string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestStreamedTextureDict(textureDict, cb)
|
||||
xLib.streaming.requestStreamedTextureDict = function(textureDict, cb)
|
||||
RequestStreamedTextureDict(textureDict, false)
|
||||
|
||||
while not HasStreamedTextureDictLoaded(textureDict) do Wait(500) end
|
||||
@@ -28,7 +28,7 @@ end
|
||||
---@param assetName string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestNamedPtfxAsset(assetName, cb)
|
||||
xLib.streaming.requestNamedPtfxAsset = function(assetName, cb)
|
||||
RequestNamedPtfxAsset(assetName)
|
||||
|
||||
while not HasNamedPtfxAssetLoaded(assetName) do Wait(500) end
|
||||
@@ -39,7 +39,7 @@ end
|
||||
---@param animSet string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestAnimSet(animSet, cb)
|
||||
xLib.streaming.requestAnimSet = function(animSet, cb)
|
||||
RequestAnimSet(animSet)
|
||||
|
||||
while not HasAnimSetLoaded(animSet) do Wait(500) end
|
||||
@@ -50,7 +50,7 @@ end
|
||||
---@param animDict string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestAnimDict(animDict, cb)
|
||||
xLib.streaming.requestAnimDict = function(animDict, cb)
|
||||
RequestAnimDict(animDict)
|
||||
|
||||
while not HasAnimDictLoaded(animDict) do Wait(500) end
|
||||
@@ -61,10 +61,24 @@ end
|
||||
---@param weaponHash number | string
|
||||
---@param cb? function
|
||||
---@return string | number | nil
|
||||
function ESX.Streaming.RequestWeaponAsset(weaponHash, cb)
|
||||
xLib.streaming.requestWeaponAsset = function(weaponHash, cb)
|
||||
RequestWeaponAsset(weaponHash, 31, 0)
|
||||
|
||||
while not HasWeaponAssetLoaded(weaponHash) do Wait(500) end
|
||||
|
||||
return cb and cb(weaponHash) or weaponHash
|
||||
end
|
||||
|
||||
---@param bankName string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
xLib.streaming.requestAudioBank = function(bankName, cb)
|
||||
RequestAudioBank(bankName, false)
|
||||
|
||||
while not RequestScriptAudioBank(bankName, false) do Wait(500) end
|
||||
|
||||
return cb and cb(bankName) or bankName
|
||||
end
|
||||
|
||||
|
||||
return xLib.streaming
|
||||
@@ -0,0 +1,147 @@
|
||||
---@class stringlib
|
||||
xLib.string = string
|
||||
|
||||
--- Normalize (trim + lowercase)
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.normalize(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:match("^%s*(.-)%s*$"):lower()
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.capitalize(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:gsub("^%l", string.upper)
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.toSnake(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:gsub("%s+", "_"):gsub("([a-z%d])([A-Z])", "%1_%2"):lower()
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.toCamel(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:lower():gsub("_%a", function(w) return w:sub(2):upper() end)
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.toPascal(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:gsub("(%a)([%w_]*)", function(first, rest)
|
||||
return first:upper() .. rest:lower()
|
||||
end):gsub("_", "")
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.escapePattern(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:gsub("([%%%^%$%(%)%.%[%]%*%+%-%?])", "%%%1")
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@return boolean
|
||||
function xLib.string.matchSafe(s, pattern)
|
||||
local ok, result = pcall(string.match, s, pattern)
|
||||
return ok and result ~= nil
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@return string
|
||||
function xLib.string.before(s, pattern)
|
||||
xLib.verify(s, 'string', true)
|
||||
local start = s:find(pattern, 1, true)
|
||||
|
||||
if not start then return s end
|
||||
|
||||
return s:sub(1, start - 1)
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@return string
|
||||
function xLib.string.after(s, pattern)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local _, finish = s:find(pattern, 1, true)
|
||||
|
||||
if not finish then return s end
|
||||
|
||||
return s:sub(finish + 1)
|
||||
end
|
||||
|
||||
|
||||
---@param s string
|
||||
---@param substr string
|
||||
---@return boolean
|
||||
function xLib.string.contains(s, substr)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
return s:find(substr, 1, true) ~= nil
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param old string
|
||||
---@param new string
|
||||
---@return string
|
||||
function xLib.string.replace(s, old, new)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local result = s:gsub(xLib.string.escapePattern(old), new)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
---@param length number
|
||||
---@return string
|
||||
function xLib.string.randomHex(length)
|
||||
local t = {}
|
||||
|
||||
for _ = 1, length do
|
||||
t[#t + 1] = string.format("%x", math.random(0, 15))
|
||||
end
|
||||
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
---@return string
|
||||
function xLib.string.uuid()
|
||||
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
|
||||
|
||||
local uuid = template:gsub("[xy]", function(c)
|
||||
local v = (c == "x") and math.random(0, 15) or math.random(8, 11)
|
||||
return string.format("%x", v)
|
||||
end)
|
||||
|
||||
return uuid
|
||||
end
|
||||
|
||||
return xLib.string
|
||||
@@ -0,0 +1,133 @@
|
||||
---@class tablelib
|
||||
xLib.table = table
|
||||
|
||||
---@param tbl table
|
||||
---@return boolean
|
||||
function xLib.table.isArray(tbl)
|
||||
xLib.verify(tbl, "table", true)
|
||||
|
||||
local count, maxIndex = 0, 0
|
||||
|
||||
for k, _ in pairs(tbl) do
|
||||
if type(k) ~= "number" or k < 1 or k % 1 ~= 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
count, maxIndex = count + 1, math.max(maxIndex, k)
|
||||
|
||||
if count > maxIndex then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param item any
|
||||
---@return any
|
||||
function xLib.table.searchForKey(tbl, item)
|
||||
xLib.verify(tbl, "table", true)
|
||||
|
||||
for k, v in pairs(tbl) do
|
||||
if v == item then
|
||||
return k
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param item any
|
||||
---@return boolean
|
||||
function xLib.table.contains(tbl, item)
|
||||
return xLib.table.searchForKey(tbl, item) ~= nil
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param filter fun(value:any, key:any):boolean
|
||||
---@return table
|
||||
function xLib.table.filter(tbl, filter)
|
||||
xLib.verify(tbl, "table", true)
|
||||
xLib.verify(filter, "function", true)
|
||||
|
||||
local result = {}
|
||||
|
||||
for k, v in pairs(tbl) do
|
||||
if filter(v, k) then
|
||||
result[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param copies? table
|
||||
---@return table
|
||||
function xLib.table.deepcopy(tbl, copies)
|
||||
xLib.verify(tbl, "table", true)
|
||||
|
||||
copies = copies or {}
|
||||
|
||||
if copies[tbl] then
|
||||
return copies[tbl]
|
||||
end
|
||||
|
||||
local copy = {}
|
||||
copies[tbl] = copy
|
||||
|
||||
for k, v in pairs(tbl) do
|
||||
copy[k] = type(v) == "table" and xLib.table.deepcopy(v, copies) or v
|
||||
|
||||
if type(v) == "table" and getmetatable(v) then
|
||||
setmetatable(copy[k], getmetatable(v))
|
||||
end
|
||||
end
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
--- https://github.com/overextended/ox_lib/blob/master/imports/table/shared.lua
|
||||
---@param t1 table
|
||||
---@param t2 table
|
||||
---@param addDuplicateNumbers boolean
|
||||
---@return table
|
||||
function xLib.table.merge(t1, t2, addDuplicateNumbers)
|
||||
xLib.verify(t1, "table", true)
|
||||
xLib.verify(t2, "table", true)
|
||||
xLib.verify(addDuplicateNumbers, "boolean", true)
|
||||
|
||||
addDuplicateNumbers = addDuplicateNumbers == nil or addDuplicateNumbers
|
||||
for k, v2 in pairs(t2) do
|
||||
local v1 = t1[k]
|
||||
local type1 = type(v1)
|
||||
local type2 = type(v2)
|
||||
|
||||
if type1 == 'table' and type2 == 'table' then
|
||||
xLib.table.merge(v1, v2, addDuplicateNumbers)
|
||||
elseif addDuplicateNumbers and (type1 == 'number' and type2 == 'number') then
|
||||
t1[k] = v1 + v2
|
||||
else
|
||||
t1[k] = v2
|
||||
end
|
||||
end
|
||||
|
||||
return t1
|
||||
end
|
||||
|
||||
function xLib.table.dump(tbl)
|
||||
if xLib.verify(tbl, 'table') then
|
||||
local s = '{ '
|
||||
for k,v in pairs(tbl) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. tbl(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(tbl)
|
||||
end
|
||||
end
|
||||
|
||||
return xLib.table
|
||||
+8
-3
@@ -1,10 +1,14 @@
|
||||
xLib.timeout = {}
|
||||
|
||||
local TimeoutCount = 0
|
||||
local CancelledTimeouts = {}
|
||||
|
||||
---@param msec number
|
||||
---@param cb function
|
||||
---@return number
|
||||
ESX.SetTimeout = function(msec, cb)
|
||||
xLib.timeout.setTimeout = function(msec, cb)
|
||||
xLib.verify(cb, "function", true)
|
||||
|
||||
local id <const> = TimeoutCount + 1
|
||||
|
||||
SetTimeout(msec, function()
|
||||
@@ -12,7 +16,6 @@ ESX.SetTimeout = function(msec, cb)
|
||||
CancelledTimeouts[id] = nil
|
||||
return
|
||||
end
|
||||
|
||||
cb()
|
||||
end)
|
||||
|
||||
@@ -23,6 +26,8 @@ end
|
||||
|
||||
---@param id number
|
||||
---@return nil
|
||||
ESX.ClearTimeout = function(id)
|
||||
xLib.timeout.clearTimeout = function(id)
|
||||
CancelledTimeouts[id] = true
|
||||
end
|
||||
|
||||
return xLib.timeout
|
||||
@@ -0,0 +1,161 @@
|
||||
---@alias CustomType 'number' | 'boolean' | 'function' | 'table' | 'string' | 'nil' | 'array' | 'int' | 'uint' | 'float' | 'char' | 'vector3' | 'vector4' | 'ped' | 'playerId' | 'vehicle' | 'prop' | 'class' | 'model'
|
||||
|
||||
---Checks if value is an array
|
||||
---@param value any
|
||||
---@return boolean
|
||||
local function isArray(value)
|
||||
if type(value) ~= 'table' then
|
||||
return false
|
||||
end
|
||||
|
||||
local i = 0
|
||||
|
||||
for _ in pairs(value) do
|
||||
i = i + 1
|
||||
if value[i] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---Checks if player id is correct.
|
||||
---@param value any
|
||||
---@return boolean
|
||||
local function isPlayerId(value)
|
||||
if type(value) ~= 'number' then
|
||||
return false
|
||||
end
|
||||
|
||||
if xLib.side == 'server' then
|
||||
return GetPlayerName(value) ~= nil
|
||||
else
|
||||
return NetworkIsPlayerActive(value)
|
||||
end
|
||||
end
|
||||
|
||||
---Checks if function is callable.
|
||||
---@param value any
|
||||
---@return boolean
|
||||
local function isCallable(value)
|
||||
local value_type = type(value)
|
||||
|
||||
if value_type == 'function' then
|
||||
return true
|
||||
end
|
||||
|
||||
if value_type == 'table' then
|
||||
local mt = getmetatable(value)
|
||||
|
||||
if mt and mt.__call then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
xLib.callback.register('xLib:validateModel', function(model)
|
||||
return IsModelValid(model)
|
||||
end)
|
||||
|
||||
local function validateModel(model)
|
||||
local players = GetPlayers()
|
||||
local source = tonumber(players[math.random(1,#players)])
|
||||
|
||||
return xLib.callback.await('xLib:validateModel', source, false, model)
|
||||
end
|
||||
|
||||
---Make sure value is a valid type
|
||||
---@param value any
|
||||
---@param valid_type CustomType
|
||||
---@return boolean
|
||||
local function verifyType(value, valid_type)
|
||||
if valid_type == 'function' then
|
||||
return isCallable(value)
|
||||
elseif valid_type == 'array' then
|
||||
return isArray(value)
|
||||
elseif valid_type == 'int' then
|
||||
return math.type(value) == 'integer'
|
||||
elseif valid_type == 'float' then
|
||||
return math.type(value) == 'float'
|
||||
elseif valid_type == 'uint' then
|
||||
return math.type(value) == 'int' and value >= 0
|
||||
elseif valid_type == 'char' then
|
||||
return type(value) == 'string' and #value == 1
|
||||
elseif valid_type == 'ped' then
|
||||
return GetEntityType(value) == 1
|
||||
elseif valid_type == 'vehicle' then
|
||||
return GetEntityType(value) == 2
|
||||
elseif valid_type == 'prop' then
|
||||
return GetEntityType(value) == 3
|
||||
elseif valid_type == 'playerId' then
|
||||
return isPlayerId(value)
|
||||
elseif valid_type == 'class' then
|
||||
if type(value) ~= 'table' then
|
||||
return false
|
||||
end
|
||||
|
||||
return getmetatable(value)?._isClass == true
|
||||
elseif valid_type == 'model' then
|
||||
if xLib.side == "server" then
|
||||
return validateModel(value)
|
||||
end
|
||||
|
||||
return IsModelValid(value)
|
||||
end
|
||||
|
||||
return type(value) == valid_type
|
||||
end
|
||||
|
||||
---@class VerifyTable
|
||||
---@field debug VerifyFunction
|
||||
|
||||
---@alias VerifyFunction fun(value: any, valid_types: CustomType[] | CustomType, throw_error?: boolean): boolean
|
||||
|
||||
---@type VerifyFunction | VerifyTable
|
||||
xLib.verify = setmetatable({
|
||||
fn = function(value, valid_types, throw_error)
|
||||
local match = false
|
||||
|
||||
if type(valid_types) == 'table' then
|
||||
for i = 1, #valid_types do
|
||||
if xLib.verify(value, valid_types[i]) then
|
||||
match = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if throw_error and not match then
|
||||
error(('[xLib] Couldn\'t match %s to types %s'):format(value, json.encode(valid_types)))
|
||||
else
|
||||
return match
|
||||
end
|
||||
end
|
||||
|
||||
match = verifyType(value, valid_types)
|
||||
|
||||
if throw_error and not match then
|
||||
error(('[xLib] Couldn\'t match %s to type %s'):format(value, valid_types))
|
||||
else
|
||||
return match
|
||||
end
|
||||
end
|
||||
}, {
|
||||
__index = function(self, k)
|
||||
if k == 'debug' and xLib.debug then
|
||||
return rawget(self, 'fn'
|
||||
)
|
||||
elseif k ~= 'debug' then
|
||||
return rawget(self, k)
|
||||
else
|
||||
return Noop
|
||||
end
|
||||
end,
|
||||
__call = function(self, ...)
|
||||
return rawget(self, 'fn')(...)
|
||||
end
|
||||
})
|
||||
|
||||
return xLib.verify
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
---@param conditionFunc function A function that is repeatedly called until it returns a truthy value or the timeout is exceeded.
|
||||
---@param errorMessage? string Optional. If set, an error will be thrown with this message if the condition is not met within the timeout. If not set, no error will be thrown.
|
||||
---@param timeoutMs? number Optional. The maximum time to wait (in milliseconds) for the condition to be met. Defaults to 1000ms.
|
||||
---@return boolean, any: Returns success status and the returned value of the condition function.
|
||||
xLib.waitFor = function(conditionFunc, errorMessage, timeoutMs)
|
||||
timeoutMs = timeoutMs or 1000
|
||||
|
||||
if timeoutMs < 0 then
|
||||
error("Timeout should be a positive number.")
|
||||
end
|
||||
|
||||
xLib.verify(conditionFunc, "function", true)
|
||||
|
||||
-- since errorMessage is optional, we only validate it if the user provided it.
|
||||
if errorMessage then
|
||||
xLib.verify(errorMessage, "string", true)
|
||||
end
|
||||
|
||||
local invokingResource = GetInvokingResource()
|
||||
local startTimeMs = GetGameTimer()
|
||||
while GetGameTimer() - startTimeMs < timeoutMs do
|
||||
local result = conditionFunc()
|
||||
|
||||
if result then
|
||||
return true, result
|
||||
end
|
||||
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
if errorMessage then
|
||||
error(("[%s] -> %s"):format(invokingResource, errorMessage))
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return xLib.waitFor
|
||||
@@ -0,0 +1,67 @@
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
local registeredCallbacks = {}
|
||||
local resource_name = GetCurrentResourceName() --TODO: Add cache
|
||||
|
||||
|
||||
AddEventHandler('onResourceStop', function(resourceName)
|
||||
if resource_name == resourceName then return end
|
||||
|
||||
for callbackName, resource in pairs(registeredCallbacks) do
|
||||
if resource == resourceName then
|
||||
registeredCallbacks[callbackName] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
---For internal use only.
|
||||
---Sets a callback event as registered to a specific resource, preventing it from
|
||||
---being overwritten. Any unknown callbacks will return an error to the caller.
|
||||
---@param callbackName string
|
||||
---@param isValid boolean
|
||||
function xLib.setValidCallback(callbackName, isValid)
|
||||
local resourceName = GetInvokingResource() or resource_name
|
||||
local callbackResource = registeredCallbacks[callbackName]
|
||||
|
||||
if callbackResource then
|
||||
if not isValid then
|
||||
callbackResource[callbackName] = nil
|
||||
return
|
||||
end
|
||||
|
||||
if callbackResource == resourceName then return end
|
||||
|
||||
local errMessage = ("^1resource '%s' attempted to overwrite callback '%s' owned by resource '%s'^0"):format(resourceName, callbackName, callbackResource)
|
||||
|
||||
return print(('^1SCRIPT ERROR: %s^0\n%s'):format(errMessage,
|
||||
Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()) or ''))
|
||||
end
|
||||
|
||||
print(("set valid callback '%s' for resource '%s'"):format(callbackName, resourceName))
|
||||
|
||||
registeredCallbacks[callbackName] = resourceName
|
||||
end
|
||||
|
||||
function xLib.isCallbackValid(callbackName)
|
||||
return registeredCallbacks[callbackName] == GetInvokingResource() or resource_name
|
||||
end
|
||||
|
||||
local cbEvent = '__xLib_cb_%s'
|
||||
|
||||
RegisterNetEvent('xLib:validateCallback', function(callbackName, invokingResource, key)
|
||||
if registeredCallbacks[callbackName] then return end
|
||||
|
||||
local event = cbEvent:format(invokingResource)
|
||||
|
||||
if GetGameName() == 'fxserver' then
|
||||
return TriggerClientEvent(event, source, key, 'cb_invalid')
|
||||
end
|
||||
|
||||
TriggerServerEvent(event, key, 'cb_invalid')
|
||||
end)
|
||||
@@ -0,0 +1,38 @@
|
||||
---@diagnostic disable: lowercase-global
|
||||
xLib = setmetatable({
|
||||
name = 'xLib',
|
||||
side = IsDuplicityVersion() and 'server' or 'client'
|
||||
}, {
|
||||
__newindex = function(self, key, fn)
|
||||
rawset(self, key, fn)
|
||||
|
||||
if debug.getinfo(2, 'S').short_src:find('@esx_lib/resource') then
|
||||
exports(key, fn)
|
||||
end
|
||||
end,
|
||||
|
||||
__index = function(self, key)
|
||||
local dir = ('imports/%s'):format(key)
|
||||
local chunk = LoadResourceFile(self.name, ('%s/%s.lua'):format(dir, self.side))
|
||||
|
||||
local shared = LoadResourceFile(self.name, ('%s/shared.lua'):format(dir))
|
||||
|
||||
if shared then
|
||||
chunk = (chunk and ('%s\n%s'):format(shared, chunk)) or shared
|
||||
end
|
||||
|
||||
if chunk then
|
||||
local fn, err = load(chunk, ('@@esx_lib/%s/%s.lua'):format(key, self.side))
|
||||
|
||||
if not fn or err then
|
||||
return error(('\n^1Error importing module (%s): %s^0'):format(dir, err), 3)
|
||||
end
|
||||
|
||||
rawset(self, key, fn() or Noop)
|
||||
|
||||
return self[key]
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
require = xLib.require
|
||||
Reference in New Issue
Block a user