299 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			299 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| ---@meta
 | |
| --[[
 | |
|     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>
 | |
| ]]
 | |
| 
 | |
| if not _VERSION:find('5.4') then
 | |
|     error('Lua 5.4 must be enabled in the resource manifest!', 2)
 | |
| end
 | |
| 
 | |
| local resourceName = GetCurrentResourceName()
 | |
| local ox_lib = 'ox_lib'
 | |
| 
 | |
| -- Some people have decided to load this file as part of ox_lib's fxmanifest?
 | |
| if resourceName == ox_lib then return end
 | |
| 
 | |
| if lib and lib.name == ox_lib then
 | |
|     error(("Cannot load ox_lib more than once.\n\tRemove any duplicate entries from '@%s/fxmanifest.lua'"):format(resourceName))
 | |
| end
 | |
| 
 | |
| local export = exports[ox_lib]
 | |
| 
 | |
| if GetResourceState(ox_lib) ~= 'started' then
 | |
|     error('^1ox_lib must be started before this resource.^0', 0)
 | |
| end
 | |
| 
 | |
| local status = export.hasLoaded()
 | |
| 
 | |
| if status ~= true then error(status, 2) end
 | |
| 
 | |
| -- Ignore invalid types during msgpack.pack (e.g. userdata)
 | |
| msgpack.setoption('ignore_invalid', true)
 | |
| 
 | |
| -----------------------------------------------------------------------------------------------
 | |
| -- Module
 | |
| -----------------------------------------------------------------------------------------------
 | |
| 
 | |
| local LoadResourceFile = LoadResourceFile
 | |
| local context = IsDuplicityVersion() and 'server' or 'client'
 | |
| 
 | |
| function noop() end
 | |
| 
 | |
| local function loadModule(self, module)
 | |
|     local dir = ('imports/%s'):format(module)
 | |
|     local chunk = LoadResourceFile(ox_lib, ('%s/%s.lua'):format(dir, context))
 | |
|     local shared = LoadResourceFile(ox_lib, ('%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, ('@@ox_lib/imports/%s/%s.lua'):format(module, context))
 | |
| 
 | |
|         if not fn or err then
 | |
|             if shared then
 | |
|                 lib.print.warn(("An error occurred when importing '@ox_lib/imports/%s'.\nThis is likely caused by improperly updating ox_lib.\n%s'")
 | |
|                     :format(module, err))
 | |
|                 fn, err = load(shared, ('@@ox_lib/imports/%s/shared.lua'):format(module))
 | |
|             end
 | |
| 
 | |
|             if not fn or err then
 | |
|                 return error(('\n^1Error importing module (%s): %s^0'):format(dir, err), 3)
 | |
|             end
 | |
|         end
 | |
| 
 | |
|         local result = fn()
 | |
|         self[module] = result or noop
 | |
|         return self[module]
 | |
|     end
 | |
| end
 | |
| 
 | |
| -----------------------------------------------------------------------------------------------
 | |
| -- API
 | |
| -----------------------------------------------------------------------------------------------
 | |
| 
 | |
| local function call(self, index, ...)
 | |
|     local module = rawget(self, index)
 | |
| 
 | |
|     if not module then
 | |
|         self[index] = noop
 | |
|         module = loadModule(self, index)
 | |
| 
 | |
|         if not module then
 | |
|             local function method(...)
 | |
|                 return export[index](nil, ...)
 | |
|             end
 | |
| 
 | |
|             if not ... then
 | |
|                 self[index] = method
 | |
|             end
 | |
| 
 | |
|             return method
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     return module
 | |
| end
 | |
| 
 | |
| local lib = setmetatable({
 | |
|     name = ox_lib,
 | |
|     context = context,
 | |
| }, {
 | |
|     __index = call,
 | |
|     __call = call,
 | |
| })
 | |
| 
 | |
| local intervals = {}
 | |
| --- Dream of a world where this PR gets accepted.
 | |
| ---@param callback function | number
 | |
| ---@param interval? number
 | |
| ---@param ... any
 | |
| function SetInterval(callback, interval, ...)
 | |
|     interval = interval or 0
 | |
| 
 | |
|     if type(interval) ~= 'number' then
 | |
|         return error(('Interval must be a number. Received %s'):format(json.encode(interval --[[@as unknown]])))
 | |
|     end
 | |
| 
 | |
|     local cbType = type(callback)
 | |
| 
 | |
|     if cbType == 'number' and intervals[callback] then
 | |
|         intervals[callback] = interval or 0
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     if cbType ~= 'function' then
 | |
|         return error(('Callback must be a function. Received %s'):format(cbType))
 | |
|     end
 | |
| 
 | |
|     local args, id = { ... }
 | |
| 
 | |
|     Citizen.CreateThreadNow(function(ref)
 | |
|         id = ref
 | |
|         intervals[id] = interval or 0
 | |
|         repeat
 | |
|             interval = intervals[id]
 | |
|             Wait(interval)
 | |
| 
 | |
|             if interval < 0 then break end
 | |
|             callback(table.unpack(args))
 | |
|         until false
 | |
|         intervals[id] = nil
 | |
|     end)
 | |
| 
 | |
|     return id
 | |
| end
 | |
| 
 | |
| ---@param id number
 | |
| function ClearInterval(id)
 | |
|     if type(id) ~= 'number' then
 | |
|         return error(('Interval id must be a number. Received %s'):format(json.encode(id --[[@as unknown]])))
 | |
|     end
 | |
| 
 | |
|     if not intervals[id] then
 | |
|         return error(('No interval exists with id %s'):format(id))
 | |
|     end
 | |
| 
 | |
|     intervals[id] = -1
 | |
| end
 | |
| 
 | |
| --[[
 | |
|     lua language server doesn't support generics when using @overload
 | |
|     see https://github.com/LuaLS/lua-language-server/issues/723
 | |
|     this function stub allows the following to work
 | |
| 
 | |
|     local key = cache('key', function() return 'abc' end) -- fff: 'abc'
 | |
|     local game = cache.game -- game: string
 | |
| ]]
 | |
| 
 | |
| ---@generic T
 | |
| ---@param key string
 | |
| ---@param func fun(...: any): T
 | |
| ---@param timeout? number
 | |
| ---@return T
 | |
| ---Caches the result of a function, optionally clearing it after timeout ms.
 | |
| function cache(key, func, timeout) end
 | |
| 
 | |
| local cacheEvents = {}
 | |
| 
 | |
| local cache = setmetatable({ game = GetGameName(), resource = resourceName }, {
 | |
|     __index = function(self, key)
 | |
|         cacheEvents[key] = {}
 | |
| 
 | |
|         AddEventHandler(('ox_lib:cache:%s'):format(key), function(value)
 | |
|             local oldValue = self[key]
 | |
|             local events = cacheEvents[key]
 | |
| 
 | |
|             for i = 1, #events do
 | |
|                 Citizen.CreateThreadNow(function()
 | |
|                     events[i](value, oldValue)
 | |
|                 end)
 | |
|             end
 | |
| 
 | |
|             self[key] = value
 | |
|         end)
 | |
| 
 | |
|         return rawset(self, key, export.cache(nil, key) or false)[key]
 | |
|     end,
 | |
| 
 | |
|     __call = function(self, key, func, timeout)
 | |
|         local value = rawget(self, key)
 | |
| 
 | |
|         if value == nil then
 | |
|             value = func()
 | |
| 
 | |
|             rawset(self, key, value)
 | |
| 
 | |
|             if timeout then SetTimeout(timeout, function() self[key] = nil end) end
 | |
|         end
 | |
| 
 | |
|         return value
 | |
|     end,
 | |
| })
 | |
| 
 | |
| function lib.onCache(key, cb)
 | |
|     if not cacheEvents[key] then
 | |
|         getmetatable(cache).__index(cache, key)
 | |
|     end
 | |
| 
 | |
|     table.insert(cacheEvents[key], cb)
 | |
| end
 | |
| 
 | |
| _ENV.lib = lib
 | |
| _ENV.cache = cache
 | |
| _ENV.require = lib.require
 | |
| 
 | |
| local notifyEvent = ('__ox_notify_%s'):format(cache.resource)
 | |
| 
 | |
| if context == 'client' then
 | |
|     RegisterNetEvent(notifyEvent, function(data)
 | |
|         if locale then
 | |
|             if data.title then
 | |
|                 data.title = locale(data.title) or data.title
 | |
|             end
 | |
| 
 | |
|             if data.description then
 | |
|                 data.description = locale(data.description) or data.description
 | |
|             end
 | |
|         end
 | |
| 
 | |
|         return export:notify(data)
 | |
|     end)
 | |
| 
 | |
|     cache.playerId = PlayerId()
 | |
|     cache.serverId = GetPlayerServerId(cache.playerId)
 | |
| else
 | |
|     ---`server`\
 | |
|     ---Trigger a notification on the target playerId from the server.\
 | |
|     ---If locales are loaded, the title and description will be formatted automatically.\
 | |
|     ---Note: No support for locale placeholders when using this function.
 | |
|     ---@param playerId number
 | |
|     ---@param data NotifyProps
 | |
|     ---@deprecated
 | |
|     ---@diagnostic disable-next-line: duplicate-set-field
 | |
|     function lib.notify(playerId, data)
 | |
|         TriggerClientEvent(notifyEvent, playerId, data)
 | |
|     end
 | |
| 
 | |
|     local poolNatives = {
 | |
|         CPed = GetAllPeds,
 | |
|         CObject = GetAllObjects,
 | |
|         CVehicle = GetAllVehicles,
 | |
|     }
 | |
| 
 | |
|     ---@param poolName 'CPed' | 'CObject' | 'CVehicle'
 | |
|     ---@return number[]
 | |
|     ---Server-side parity for the `GetGamePool` client native.
 | |
|     function GetGamePool(poolName)
 | |
|         local fn = poolNatives[poolName]
 | |
|         return fn and fn() --[[@as number[] ]]
 | |
|     end
 | |
| 
 | |
|     ---@return number[]
 | |
|     ---Server-side parity for the `GetPlayers` client native.
 | |
|     function GetActivePlayers()
 | |
|         local playerNum = GetNumPlayerIndices()
 | |
|         local players = table.create(playerNum, 0)
 | |
| 
 | |
|         for i = 1, playerNum do
 | |
|             players[i] = tonumber(GetPlayerFromIndex(i - 1))
 | |
|         end
 | |
| 
 | |
|         return players
 | |
|     end
 | |
| end
 | |
| 
 | |
| for i = 1, GetNumResourceMetadata(cache.resource, 'ox_lib') do
 | |
|     local name = GetResourceMetadata(cache.resource, 'ox_lib', i - 1)
 | |
| 
 | |
|     if not rawget(lib, name) then
 | |
|         local module = loadModule(lib, name)
 | |
| 
 | |
|         if type(module) == 'function' then pcall(module) end
 | |
|     end
 | |
| end
 | 
