update libs
This commit is contained in:
parent
ff18b299fd
commit
e4b7186910
@ -6,7 +6,7 @@ rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aw
|
|||||||
|
|
||||||
name 'ox_lib'
|
name 'ox_lib'
|
||||||
author 'Overextended'
|
author 'Overextended'
|
||||||
version '3.26.0'
|
version '3.30.3'
|
||||||
license 'LGPL-3.0-or-later'
|
license 'LGPL-3.0-or-later'
|
||||||
repository 'https://github.com/overextended/ox_lib'
|
repository 'https://github.com/overextended/ox_lib'
|
||||||
description 'A library of shared functions to utilise in other resources.'
|
description 'A library of shared functions to utilise in other resources.'
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
---@class Array : OxClass
|
---@class Array<T> : OxClass, { [number]: T }
|
||||||
lib.array = lib.class('Array')
|
lib.array = lib.class('Array')
|
||||||
|
|
||||||
|
local table_unpack = table.unpack
|
||||||
|
local table_remove = table.remove
|
||||||
|
local table_clone = table.clone
|
||||||
|
local table_concat = table.concat
|
||||||
|
local table_type = table.type
|
||||||
|
|
||||||
---@alias ArrayLike<T> Array | { [number]: T }
|
---@alias ArrayLike<T> Array | { [number]: T }
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
@ -19,18 +25,63 @@ function lib.array:__newindex(index, value)
|
|||||||
rawset(self, index, value)
|
rawset(self, index, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Create a new array containing the elements from two arrays.
|
---Creates a new array from an iteratable value.
|
||||||
---@param arr ArrayLike
|
---@param iter table | function | string
|
||||||
function lib.array:merge(arr)
|
---@return Array
|
||||||
local newArr = table.clone(self)
|
function lib.array:from(iter)
|
||||||
local length = #self
|
local iterType = type(iter)
|
||||||
|
|
||||||
for i = 1, #arr do
|
if iterType == 'table' then
|
||||||
length += 1
|
return lib.array:new(table_unpack(iter))
|
||||||
newArr[length] = arr[i]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return lib.array:new(table.unpack(newArr))
|
if iterType == 'string' then
|
||||||
|
return lib.array:new(string.strsplit('', iter))
|
||||||
|
end
|
||||||
|
|
||||||
|
if iterType == 'function' then
|
||||||
|
local arr = lib.array:new()
|
||||||
|
local length = 0
|
||||||
|
|
||||||
|
for value in iter do
|
||||||
|
length += 1
|
||||||
|
arr[length] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
return arr
|
||||||
|
end
|
||||||
|
|
||||||
|
error(('Array.from argument was not a valid iterable value (received %s)'):format(iterType))
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns the element at the given index, with negative numbers counting backwards from the end of the array.
|
||||||
|
---@param index number
|
||||||
|
---@return unknown
|
||||||
|
function lib.array:at(index)
|
||||||
|
if index < 0 then
|
||||||
|
index = #self + index + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return self[index]
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create a new array containing the elements of two or more arrays.
|
||||||
|
---@param ... ArrayLike
|
||||||
|
function lib.array:merge(...)
|
||||||
|
local newArr = table_clone(self)
|
||||||
|
local length = #self
|
||||||
|
local arrays = { ... }
|
||||||
|
|
||||||
|
for i = 1, #arrays do
|
||||||
|
local arr = arrays[i]
|
||||||
|
|
||||||
|
for j = 1, #arr do
|
||||||
|
length += 1
|
||||||
|
newArr[length] = arr[j]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib.array:new(table_unpack(newArr))
|
||||||
end
|
end
|
||||||
|
|
||||||
---Tests if all elements in an array succeed in passing the provided test function.
|
---Tests if all elements in an array succeed in passing the provided test function.
|
||||||
@ -45,7 +96,26 @@ function lib.array:every(testFn)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---Creates a new array containing the elements from an array thtat pass the test of the provided function.
|
---Sets all elements within a range to the given value and returns the modified array.
|
||||||
|
---@param value any
|
||||||
|
---@param start? number
|
||||||
|
---@param endIndex? number
|
||||||
|
function lib.array:fill(value, start, endIndex)
|
||||||
|
local length = #self
|
||||||
|
start = start or 1
|
||||||
|
endIndex = endIndex or length
|
||||||
|
|
||||||
|
if start < 1 then start = 1 end
|
||||||
|
if endIndex > length then endIndex = length end
|
||||||
|
|
||||||
|
for i = start, endIndex do
|
||||||
|
self[i] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---Creates a new array containing the elements from an array that pass the test of the provided function.
|
||||||
---@param testFn fun(element: unknown): boolean
|
---@param testFn fun(element: unknown): boolean
|
||||||
function lib.array:filter(testFn)
|
function lib.array:filter(testFn)
|
||||||
local newArr = {}
|
local newArr = {}
|
||||||
@ -60,7 +130,7 @@ function lib.array:filter(testFn)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return lib.array:new(table.unpack(newArr))
|
return lib.array:new(table_unpack(newArr))
|
||||||
end
|
end
|
||||||
|
|
||||||
---Returns the first or last element of an array that passes the provided test function.
|
---Returns the first or last element of an array that passes the provided test function.
|
||||||
@ -109,7 +179,7 @@ function lib.array:indexOf(value, last)
|
|||||||
local element = self[i]
|
local element = self[i]
|
||||||
|
|
||||||
if element == value then
|
if element == value then
|
||||||
return element
|
return i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -122,15 +192,38 @@ function lib.array:forEach(cb)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Determines if a given element exists inside an array.
|
||||||
|
---@param element unknown The value to find in the array.
|
||||||
|
---@param fromIndex? number The position in the array to begin searching from.
|
||||||
|
function lib.array:includes(element, fromIndex)
|
||||||
|
for i = (fromIndex or 1), #self do
|
||||||
|
if self[i] == element then return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
---Concatenates all array elements into a string, seperated by commas or the specified seperator.
|
---Concatenates all array elements into a string, seperated by commas or the specified seperator.
|
||||||
---@param seperator? string
|
---@param seperator? string
|
||||||
function lib.array:join(seperator)
|
function lib.array:join(seperator)
|
||||||
return table.concat(self, seperator or ',')
|
return table_concat(self, seperator or ',')
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create a new array containing the results from calling the provided function on every element in an array.
|
||||||
|
---@param cb fun(element: unknown, index: number, array: self): unknown
|
||||||
|
function lib.array:map(cb)
|
||||||
|
local arr = {}
|
||||||
|
|
||||||
|
for i = 1, #self do
|
||||||
|
arr[i] = cb(self[i], i, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib.array:new(table_unpack(arr))
|
||||||
end
|
end
|
||||||
|
|
||||||
---Removes the last element from an array and returns the removed element.
|
---Removes the last element from an array and returns the removed element.
|
||||||
function lib.array:pop()
|
function lib.array:pop()
|
||||||
return table.remove(self)
|
return table_remove(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Adds the given elements to the end of an array and returns the new array length.
|
---Adds the given elements to the end of an array and returns the new array length.
|
||||||
@ -147,35 +240,110 @@ function lib.array:push(...)
|
|||||||
return length
|
return length
|
||||||
end
|
end
|
||||||
|
|
||||||
---Removes the first element from an array and returns the removed element.
|
---The "reducer" function is applied to every element within an array, with the previous element's result serving as the accumulator.
|
||||||
function lib.array:shift()
|
|
||||||
return table.remove(self, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
---The "reducer" function is applied to every element within an array, with the previous element's result serving as the accumulator.\
|
|
||||||
---If an initial value is provided, it's used as the accumulator for index 1; otherwise, index 1 itself serves as the initial value, and iteration begins from index 2.
|
---If an initial value is provided, it's used as the accumulator for index 1; otherwise, index 1 itself serves as the initial value, and iteration begins from index 2.
|
||||||
---@generic T
|
---@generic T
|
||||||
---@param reducer fun(accumulator: T, currentValue: T, index?: number): T
|
---@param reducer fun(accumulator: T, currentValue: T, index?: number): T
|
||||||
---@param initialValue? T
|
---@param initialValue? T
|
||||||
|
---@param reverse? boolean Iterate over the array from right-to-left.
|
||||||
---@return T
|
---@return T
|
||||||
function lib.array:reduce(reducer, initialValue)
|
function lib.array:reduce(reducer, initialValue, reverse)
|
||||||
|
local length = #self
|
||||||
local initialIndex = initialValue and 1 or 2
|
local initialIndex = initialValue and 1 or 2
|
||||||
local accumulator = initialValue or self[1]
|
local accumulator = initialValue or self[1]
|
||||||
|
|
||||||
for i = initialIndex, #self do
|
if reverse then
|
||||||
accumulator = reducer(accumulator, self[i], i)
|
for i = initialIndex, length do
|
||||||
|
local index = length - i + initialIndex
|
||||||
|
accumulator = reducer(accumulator, self[index], index)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i = initialIndex, length do
|
||||||
|
accumulator = reducer(accumulator, self[i], i)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return accumulator
|
return accumulator
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Reverses the elements inside an array.
|
||||||
|
function lib.array:reverse()
|
||||||
|
local i, j = 1, #self
|
||||||
|
|
||||||
|
while i < j do
|
||||||
|
self[i], self[j] = self[j], self[i]
|
||||||
|
i += 1
|
||||||
|
j -= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---Removes the first element from an array and returns the removed element.
|
||||||
|
function lib.array:shift()
|
||||||
|
return table_remove(self, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Creates a shallow copy of a portion of an array as a new array.
|
||||||
|
---@param start? number
|
||||||
|
---@param finish? number
|
||||||
|
function lib.array:slice(start, finish)
|
||||||
|
local length = #self
|
||||||
|
start = start or 1
|
||||||
|
finish = finish or length
|
||||||
|
|
||||||
|
if start < 0 then start = length + start + 1 end
|
||||||
|
if finish < 0 then finish = length + finish + 1 end
|
||||||
|
if start < 1 then start = 1 end
|
||||||
|
if finish > length then finish = length end
|
||||||
|
|
||||||
|
local arr = lib.array:new()
|
||||||
|
local index = 0
|
||||||
|
|
||||||
|
for i = start, finish do
|
||||||
|
index += 1
|
||||||
|
arr[index] = self[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
return arr
|
||||||
|
end
|
||||||
|
|
||||||
|
---Creates a new array with reversed elements from the given array.
|
||||||
|
function lib.array:toReversed()
|
||||||
|
local reversed = lib.array:new()
|
||||||
|
|
||||||
|
for i = #self, 1, -1 do
|
||||||
|
reversed:push(self[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
return reversed
|
||||||
|
end
|
||||||
|
|
||||||
|
---Inserts the given elements to the start of an array and returns the new array length.
|
||||||
|
---@param ... any
|
||||||
|
function lib.array:unshift(...)
|
||||||
|
local elements = { ... }
|
||||||
|
local length = #self
|
||||||
|
local eLength = #elements
|
||||||
|
|
||||||
|
for i = length, 1, -1 do
|
||||||
|
self[i + eLength] = self[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #elements do
|
||||||
|
self[i] = elements[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
return length + eLength
|
||||||
|
end
|
||||||
|
|
||||||
---Returns true if the given table is an instance of array or an array-like table.
|
---Returns true if the given table is an instance of array or an array-like table.
|
||||||
---@param tbl ArrayLike
|
---@param tbl ArrayLike
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function lib.array.isArray(tbl)
|
function lib.array.isArray(tbl)
|
||||||
if not type(tbl) == 'table' then return false end
|
local tableType = table_type(tbl)
|
||||||
|
|
||||||
local tableType = table.type(tbl)
|
if not tableType then return false end
|
||||||
|
|
||||||
if tableType == 'array' or tableType == 'empty' or lib.array.instanceOf(tbl, lib.array) then
|
if tableType == 'array' or tableType == 'empty' or lib.array.instanceOf(tbl, lib.array) then
|
||||||
return true
|
return true
|
||||||
|
|||||||
@ -5,9 +5,12 @@ local callbackTimeout = GetConvarInt('ox:callbackTimeout', 300000)
|
|||||||
|
|
||||||
RegisterNetEvent(cbEvent:format(cache.resource), function(key, ...)
|
RegisterNetEvent(cbEvent:format(cache.resource), function(key, ...)
|
||||||
local cb = pendingCallbacks[key]
|
local cb = pendingCallbacks[key]
|
||||||
|
|
||||||
|
if not cb then return end
|
||||||
|
|
||||||
pendingCallbacks[key] = nil
|
pendingCallbacks[key] = nil
|
||||||
|
|
||||||
return cb and cb(...)
|
cb(...)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
---@param event string
|
---@param event string
|
||||||
@ -41,12 +44,19 @@ local function triggerServerCallback(_, event, delay, cb, ...)
|
|||||||
key = ('%s:%s'):format(event, math.random(0, 100000))
|
key = ('%s:%s'):format(event, math.random(0, 100000))
|
||||||
until not pendingCallbacks[key]
|
until not pendingCallbacks[key]
|
||||||
|
|
||||||
|
TriggerServerEvent('ox_lib:validateCallback', event, cache.resource, key)
|
||||||
TriggerServerEvent(cbEvent:format(event), cache.resource, key, ...)
|
TriggerServerEvent(cbEvent:format(event), cache.resource, key, ...)
|
||||||
|
|
||||||
---@type promise | false
|
---@type promise | false
|
||||||
local promise = not cb and promise.new()
|
local promise = not cb and promise.new()
|
||||||
|
|
||||||
pendingCallbacks[key] = function(response, ...)
|
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, ... }
|
response = { response, ... }
|
||||||
|
|
||||||
if promise then
|
if promise then
|
||||||
@ -74,6 +84,10 @@ lib.callback = setmetatable({}, {
|
|||||||
else
|
else
|
||||||
local cbType = type(cb)
|
local cbType = type(cb)
|
||||||
|
|
||||||
|
if cbType == 'table' and getmetatable(cb)?.__call then
|
||||||
|
cbType = 'function'
|
||||||
|
end
|
||||||
|
|
||||||
assert(cbType == 'function', ("expected argument 3 to have type 'function' (received %s)"):format(cbType))
|
assert(cbType == 'function', ("expected argument 3 to have type 'function' (received %s)"):format(cbType))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -109,7 +123,11 @@ local pcall = pcall
|
|||||||
---Registers an event handler and callback function to respond to server requests.
|
---Registers an event handler and callback function to respond to server requests.
|
||||||
---@diagnostic disable-next-line: duplicate-set-field
|
---@diagnostic disable-next-line: duplicate-set-field
|
||||||
function lib.callback.register(name, cb)
|
function lib.callback.register(name, cb)
|
||||||
RegisterNetEvent(cbEvent:format(name), function(resource, key, ...)
|
event = cbEvent:format(name)
|
||||||
|
|
||||||
|
lib.setValidCallback(name, true)
|
||||||
|
|
||||||
|
RegisterNetEvent(event, function(resource, key, ...)
|
||||||
TriggerServerEvent(cbEvent:format(resource), key, callbackResponse(pcall(cb, ...)))
|
TriggerServerEvent(cbEvent:format(resource), key, callbackResponse(pcall(cb, ...)))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,9 +4,12 @@ local callbackTimeout = GetConvarInt('ox:callbackTimeout', 300000)
|
|||||||
|
|
||||||
RegisterNetEvent(cbEvent:format(cache.resource), function(key, ...)
|
RegisterNetEvent(cbEvent:format(cache.resource), function(key, ...)
|
||||||
local cb = pendingCallbacks[key]
|
local cb = pendingCallbacks[key]
|
||||||
|
|
||||||
|
if not cb then return end
|
||||||
|
|
||||||
pendingCallbacks[key] = nil
|
pendingCallbacks[key] = nil
|
||||||
|
|
||||||
return cb and cb(...)
|
cb(...)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
---@param _ any
|
---@param _ any
|
||||||
@ -24,12 +27,19 @@ local function triggerClientCallback(_, event, playerId, cb, ...)
|
|||||||
key = ('%s:%s:%s'):format(event, math.random(0, 100000), playerId)
|
key = ('%s:%s:%s'):format(event, math.random(0, 100000), playerId)
|
||||||
until not pendingCallbacks[key]
|
until not pendingCallbacks[key]
|
||||||
|
|
||||||
|
TriggerClientEvent('ox_lib:validateCallback', playerId, event, cache.resource, key)
|
||||||
TriggerClientEvent(cbEvent:format(event), playerId, cache.resource, key, ...)
|
TriggerClientEvent(cbEvent:format(event), playerId, cache.resource, key, ...)
|
||||||
|
|
||||||
---@type promise | false
|
---@type promise | false
|
||||||
local promise = not cb and promise.new()
|
local promise = not cb and promise.new()
|
||||||
|
|
||||||
pendingCallbacks[key] = function(response, ...)
|
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, ... }
|
response = { response, ... }
|
||||||
|
|
||||||
if promise then
|
if promise then
|
||||||
@ -57,6 +67,10 @@ lib.callback = setmetatable({}, {
|
|||||||
else
|
else
|
||||||
local cbType = type(cb)
|
local cbType = type(cb)
|
||||||
|
|
||||||
|
if cbType == 'table' and getmetatable(cb)?.__call then
|
||||||
|
cbType = 'function'
|
||||||
|
end
|
||||||
|
|
||||||
assert(cbType == 'function', ("expected argument 3 to have type 'function' (received %s)"):format(cbType))
|
assert(cbType == 'function', ("expected argument 3 to have type 'function' (received %s)"):format(cbType))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -92,7 +106,11 @@ local pcall = pcall
|
|||||||
---Registers an event handler and callback function to respond to client requests.
|
---Registers an event handler and callback function to respond to client requests.
|
||||||
---@diagnostic disable-next-line: duplicate-set-field
|
---@diagnostic disable-next-line: duplicate-set-field
|
||||||
function lib.callback.register(name, cb)
|
function lib.callback.register(name, cb)
|
||||||
RegisterNetEvent(cbEvent:format(name), function(resource, key, ...)
|
event = cbEvent:format(name)
|
||||||
|
|
||||||
|
lib.setValidCallback(name, true)
|
||||||
|
|
||||||
|
RegisterNetEvent(event, function(resource, key, ...)
|
||||||
TriggerClientEvent(cbEvent:format(resource), source, key, callbackResponse(pcall(cb, source, ...)))
|
TriggerClientEvent(cbEvent:format(resource), source, key, callbackResponse(pcall(cb, source, ...)))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -55,10 +55,8 @@ local function void() return '' end
|
|||||||
---@return T
|
---@return T
|
||||||
function mixins.new(class, ...)
|
function mixins.new(class, ...)
|
||||||
local constructor = getConstructor(class)
|
local constructor = getConstructor(class)
|
||||||
|
local private = {}
|
||||||
local obj = setmetatable({
|
local obj = setmetatable({ private = private }, class)
|
||||||
private = {}
|
|
||||||
}, class)
|
|
||||||
|
|
||||||
if constructor then
|
if constructor then
|
||||||
local parent = class
|
local parent = class
|
||||||
@ -75,8 +73,8 @@ function mixins.new(class, ...)
|
|||||||
|
|
||||||
rawset(obj, 'super', nil)
|
rawset(obj, 'super', nil)
|
||||||
|
|
||||||
if next(obj.private) then
|
if private ~= obj.private or next(obj.private) then
|
||||||
local private = table.clone(obj.private)
|
private = table.clone(obj.private)
|
||||||
|
|
||||||
table.wipe(obj.private)
|
table.wipe(obj.private)
|
||||||
setmetatable(obj.private, {
|
setmetatable(obj.private, {
|
||||||
|
|||||||
@ -7,28 +7,27 @@ local currentDate = {}
|
|||||||
setmetatable(currentDate, {
|
setmetatable(currentDate, {
|
||||||
__index = function(self, index)
|
__index = function(self, index)
|
||||||
local newDate = os.date('*t') --[[@as Date]]
|
local newDate = os.date('*t') --[[@as Date]]
|
||||||
|
|
||||||
for k, v in pairs(newDate) do
|
for k, v in pairs(newDate) do
|
||||||
self[k] = v
|
self[k] = v
|
||||||
end
|
end
|
||||||
|
|
||||||
SetTimeout(1000, function() table.wipe(self) end)
|
SetTimeout(1000, function() table.wipe(self) end)
|
||||||
|
|
||||||
return self[index]
|
return self[index]
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
---@class OxTaskProperties
|
---@class OxTaskProperties
|
||||||
---@field minute? number | string
|
---@field minute? number|string|function
|
||||||
---@field hour? number | string
|
---@field hour? number|string|function
|
||||||
---@field day? number | string
|
---@field day? number|string|function
|
||||||
---@field month? number | string
|
---@field month? number|string|function
|
||||||
---@field year? number | string
|
---@field year? number|string|function
|
||||||
---@field weekday? number | string
|
---@field weekday? number|string|function
|
||||||
---@field job fun(task: OxTask, date: osdate)
|
---@field job fun(task: OxTask, date: osdate)
|
||||||
---@field isActive boolean
|
---@field isActive boolean
|
||||||
---@field id number
|
---@field id number
|
||||||
---@field debug? boolean
|
---@field debug? boolean
|
||||||
|
---@field lastRun? number
|
||||||
|
---@field maxDelay? number Maximum allowed delay in seconds before skipping (0 to disable)
|
||||||
|
|
||||||
---@class OxTask : OxTaskProperties
|
---@class OxTask : OxTaskProperties
|
||||||
---@field expression string
|
---@field expression string
|
||||||
@ -36,6 +35,14 @@ setmetatable(currentDate, {
|
|||||||
local OxTask = {}
|
local OxTask = {}
|
||||||
OxTask.__index = OxTask
|
OxTask.__index = OxTask
|
||||||
|
|
||||||
|
local validRanges = {
|
||||||
|
min = { min = 0, max = 59 },
|
||||||
|
hour = { min = 0, max = 23 },
|
||||||
|
day = { min = 1, max = 31 },
|
||||||
|
month = { min = 1, max = 12 },
|
||||||
|
wday = { min = 0, max = 7 },
|
||||||
|
}
|
||||||
|
|
||||||
local maxUnits = {
|
local maxUnits = {
|
||||||
min = 60,
|
min = 60,
|
||||||
hour = 24,
|
hour = 24,
|
||||||
@ -44,7 +51,23 @@ local maxUnits = {
|
|||||||
month = 12,
|
month = 12,
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Gets the amount of days in certain month
|
local weekdayMap = {
|
||||||
|
sun = 1,
|
||||||
|
mon = 2,
|
||||||
|
tue = 3,
|
||||||
|
wed = 4,
|
||||||
|
thu = 5,
|
||||||
|
fri = 6,
|
||||||
|
sat = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
local monthMap = {
|
||||||
|
jan = 1, feb = 2, mar = 3, apr = 4,
|
||||||
|
may = 5, jun = 6, jul = 7, aug = 8,
|
||||||
|
sep = 9, oct = 10, nov = 11, dec = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
---Returns the last day of the specified month
|
||||||
---@param month number
|
---@param month number
|
||||||
---@param year? number
|
---@param year? number
|
||||||
---@return number
|
---@return number
|
||||||
@ -52,9 +75,96 @@ local function getMaxDaysInMonth(month, year)
|
|||||||
return os.date('*t', os.time({ year = year or currentDate.year, month = month + 1, day = -1 })).day --[[@as number]]
|
return os.date('*t', os.time({ year = year or currentDate.year, month = month + 1, day = -1 })).day --[[@as number]]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param value string | number | nil
|
---@param value string|number
|
||||||
---@param unit string
|
---@param unit string
|
||||||
---@return string | number | false | nil
|
---@return boolean
|
||||||
|
local function isValueInRange(value, unit)
|
||||||
|
local range = validRanges[unit]
|
||||||
|
if not range then return true end
|
||||||
|
return value >= range.min and value <= range.max
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param value string
|
||||||
|
---@param unit string
|
||||||
|
---@return number|string|function|nil
|
||||||
|
local function parseCron(value, unit)
|
||||||
|
if not value or value == '*' then return end
|
||||||
|
|
||||||
|
if unit == 'day' and value:lower() == 'l' then
|
||||||
|
return function()
|
||||||
|
return getMaxDaysInMonth(currentDate.month, currentDate.year)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local num = tonumber(value)
|
||||||
|
if num then
|
||||||
|
if not isValueInRange(num, unit) then
|
||||||
|
error(("^1invalid cron expression. '%s' is out of range for %s^0"):format(value, unit), 3)
|
||||||
|
end
|
||||||
|
return num
|
||||||
|
end
|
||||||
|
|
||||||
|
if unit == 'wday' then
|
||||||
|
local start, stop = value:match('(%a+)-(%a+)')
|
||||||
|
if start and stop then
|
||||||
|
start = weekdayMap[start:lower()]
|
||||||
|
stop = weekdayMap[stop:lower()]
|
||||||
|
if start and stop then
|
||||||
|
if stop < start then stop = stop + 7 end
|
||||||
|
return ('%d-%d'):format(start, stop)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local day = weekdayMap[value:lower()]
|
||||||
|
if day then return day end
|
||||||
|
end
|
||||||
|
|
||||||
|
if unit == 'month' then
|
||||||
|
local months = {}
|
||||||
|
for month in value:gmatch('[^,]+') do
|
||||||
|
local monthNum = monthMap[month:lower()]
|
||||||
|
if monthNum then
|
||||||
|
months[#months + 1] = tostring(monthNum)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #months > 0 then
|
||||||
|
return table.concat(months, ',')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local stepMatch = value:match('^%*/(%d+)$')
|
||||||
|
if stepMatch then
|
||||||
|
local step = tonumber(stepMatch)
|
||||||
|
if not step or step == 0 then
|
||||||
|
error(("^1invalid cron expression. Step value cannot be %s^0"):format(step or 'nil'), 3)
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
local start, stop = value:match('^(%d+)-(%d+)$')
|
||||||
|
if start and stop then
|
||||||
|
start, stop = tonumber(start), tonumber(stop)
|
||||||
|
if not start or not stop or not isValueInRange(start, unit) or not isValueInRange(stop, unit) then
|
||||||
|
error(("^1invalid cron expression. Range '%s' is invalid for %s^0"):format(value, unit), 3)
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
local valid = true
|
||||||
|
for item in value:gmatch('[^,]+') do
|
||||||
|
local num = tonumber(item)
|
||||||
|
if not num or not isValueInRange(num, unit) then
|
||||||
|
valid = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if valid then return value end
|
||||||
|
|
||||||
|
error(("^1invalid cron expression. '%s' is not supported for %s^0"):format(value, unit), 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param value string|number|function|nil
|
||||||
|
---@param unit string
|
||||||
|
---@return number|false|nil
|
||||||
local function getTimeUnit(value, unit)
|
local function getTimeUnit(value, unit)
|
||||||
local currentTime = currentDate[unit]
|
local currentTime = currentDate[unit]
|
||||||
|
|
||||||
@ -62,25 +172,24 @@ local function getTimeUnit(value, unit)
|
|||||||
return unit == 'min' and currentTime + 1 or currentTime
|
return unit == 'min' and currentTime + 1 or currentTime
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if type(value) == 'function' then
|
||||||
|
return value()
|
||||||
|
end
|
||||||
|
|
||||||
local unitMax = maxUnits[unit]
|
local unitMax = maxUnits[unit]
|
||||||
|
|
||||||
if type(value) == 'string' then
|
if type(value) == 'string' then
|
||||||
local stepValue = string.match(value, '*/(%d+)')
|
local stepValue = string.match(value, '*/(%d+)')
|
||||||
|
|
||||||
if stepValue then
|
if stepValue then
|
||||||
-- */10 * * * * is equal to a list of 0,10,20,30,40,50
|
local step = tonumber(stepValue)
|
||||||
-- best suited to factors of unitMax (excluding the highest and lowest numbers)
|
|
||||||
-- i.e. for minutes - 2, 3, 4, 5, 6, 10, 12, 15, 20, 30
|
|
||||||
for i = currentTime + 1, unitMax do
|
for i = currentTime + 1, unitMax do
|
||||||
-- if i is divisible by stepValue
|
if i % step == 0 then return i end
|
||||||
if i % stepValue == 0 then return i end
|
|
||||||
end
|
end
|
||||||
|
return step + unitMax
|
||||||
return stepValue + unitMax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local range = string.match(value, '%d+-%d+')
|
local range = string.match(value, '%d+-%d+')
|
||||||
|
|
||||||
if range then
|
if range then
|
||||||
local min, max = string.strsplit('-', range)
|
local min, max = string.strsplit('-', range)
|
||||||
min, max = tonumber(min, 10), tonumber(max, 10)
|
min, max = tonumber(min, 10), tonumber(max, 10)
|
||||||
@ -97,12 +206,15 @@ local function getTimeUnit(value, unit)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local list = string.match(value, '%d+,%d+')
|
local list = string.match(value, '%d+,%d+')
|
||||||
|
|
||||||
if list then
|
if list then
|
||||||
for listValue in string.gmatch(value, '%d+') --[[@as number]] do
|
local values = {}
|
||||||
listValue = tonumber(listValue)
|
for listValue in string.gmatch(value, '%d+') do
|
||||||
|
values[#values + 1] = tonumber(listValue)
|
||||||
|
end
|
||||||
|
table.sort(values)
|
||||||
|
|
||||||
-- e.g. if current time is less than in the expression 0,10,20,45 * * * *
|
for i = 1, #values do
|
||||||
|
local listValue = values[i]
|
||||||
if unit == 'min' then
|
if unit == 'min' then
|
||||||
if currentTime < listValue then
|
if currentTime < listValue then
|
||||||
return listValue
|
return listValue
|
||||||
@ -112,56 +224,47 @@ local function getTimeUnit(value, unit)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- if iterator failed, return the first value in the list
|
return values[1] + unitMax
|
||||||
return tonumber(string.match(value, '%d+')) + unitMax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
if unit == 'min' then
|
if unit == 'min' then
|
||||||
return value <= currentTime and value + unitMax or value
|
return value <= currentTime and value + unitMax or value --[[@as number]]
|
||||||
end
|
end
|
||||||
|
|
||||||
return value < currentTime and value + unitMax or value
|
return value < currentTime and value + unitMax or value --[[@as number]]
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get a timestamp for the next time to run the task today.
|
|
||||||
---@return number?
|
---@return number?
|
||||||
function OxTask:getNextTime()
|
function OxTask:getNextTime()
|
||||||
if not self.isActive then return end
|
if not self.isActive then return end
|
||||||
|
|
||||||
local day = getTimeUnit(self.day, 'day')
|
local day = getTimeUnit(self.day, 'day')
|
||||||
|
|
||||||
-- If current day is the last day of the month, and the task is scheduled for the last day of the month, then the task should run.
|
|
||||||
if day == 0 then
|
if day == 0 then
|
||||||
-- Should probably be used month from getTimeUnit, but don't want to reorder this code.
|
|
||||||
day = getMaxDaysInMonth(currentDate.month)
|
day = getMaxDaysInMonth(currentDate.month)
|
||||||
end
|
end
|
||||||
|
|
||||||
if day ~= currentDate.day then return end
|
if day ~= currentDate.day then return end
|
||||||
|
|
||||||
local month = getTimeUnit(self.month, 'month')
|
local month = getTimeUnit(self.month, 'month')
|
||||||
|
|
||||||
if month ~= currentDate.month then return end
|
if month ~= currentDate.month then return end
|
||||||
|
|
||||||
local weekday = getTimeUnit(self.weekday, 'wday')
|
local weekday = getTimeUnit(self.weekday, 'wday')
|
||||||
|
if weekday and weekday ~= currentDate.wday then return end
|
||||||
if weekday ~= currentDate.wday then return end
|
|
||||||
|
|
||||||
local minute = getTimeUnit(self.minute, 'min')
|
local minute = getTimeUnit(self.minute, 'min')
|
||||||
|
|
||||||
if not minute then return end
|
if not minute then return end
|
||||||
|
|
||||||
local hour = getTimeUnit(self.hour, 'hour')
|
local hour = getTimeUnit(self.hour, 'hour')
|
||||||
|
|
||||||
if not hour then return end
|
if not hour then return end
|
||||||
|
|
||||||
if minute >= maxUnits.min then
|
if minute >= maxUnits.min then
|
||||||
if not self.hour then
|
if not self.hour then
|
||||||
hour += math.floor(minute / maxUnits.min)
|
hour += math.floor(minute / maxUnits.min)
|
||||||
end
|
end
|
||||||
|
|
||||||
minute = minute % maxUnits.min
|
minute = minute % maxUnits.min
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -169,20 +272,31 @@ function OxTask:getNextTime()
|
|||||||
if not self.day then
|
if not self.day then
|
||||||
day += math.floor(hour / maxUnits.hour)
|
day += math.floor(hour / maxUnits.hour)
|
||||||
end
|
end
|
||||||
|
|
||||||
hour = hour % maxUnits.hour
|
hour = hour % maxUnits.hour
|
||||||
end
|
end
|
||||||
|
|
||||||
return os.time({
|
local nextTime = os.time({
|
||||||
min = minute,
|
min = minute,
|
||||||
hour = hour,
|
hour = hour,
|
||||||
day = day or currentDate.day,
|
day = day or currentDate.day,
|
||||||
month = month or currentDate.month,
|
month = month or currentDate.month,
|
||||||
year = currentDate.year,
|
year = currentDate.year,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.lastRun and nextTime - self.lastRun < 60 then
|
||||||
|
if self.debug then
|
||||||
|
lib.print.debug(('Preventing duplicate execution of task %s - Last run: %s, Next scheduled: %s'):format(
|
||||||
|
self.id,
|
||||||
|
os.date('%c', self.lastRun),
|
||||||
|
os.date('%c', nextTime)
|
||||||
|
))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return nextTime
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get timestamp for next time to run task at any day.
|
|
||||||
---@return number
|
---@return number
|
||||||
function OxTask:getAbsoluteNextTime()
|
function OxTask:getAbsoluteNextTime()
|
||||||
local minute = getTimeUnit(self.minute, 'min')
|
local minute = getTimeUnit(self.minute, 'min')
|
||||||
@ -191,7 +305,6 @@ function OxTask:getAbsoluteNextTime()
|
|||||||
local month = getTimeUnit(self.month, 'month')
|
local month = getTimeUnit(self.month, 'month')
|
||||||
local year = getTimeUnit(self.year, 'year')
|
local year = getTimeUnit(self.year, 'year')
|
||||||
|
|
||||||
-- To avoid modifying getTimeUnit function, the day is adjusted here if needed.
|
|
||||||
if self.day then
|
if self.day then
|
||||||
if currentDate.hour < hour or (currentDate.hour == hour and currentDate.min < minute) then
|
if currentDate.hour < hour or (currentDate.hour == hour and currentDate.min < minute) then
|
||||||
day = day - 1
|
day = day - 1
|
||||||
@ -209,7 +322,6 @@ function OxTask:getAbsoluteNextTime()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if time will be in next year.
|
|
||||||
---@diagnostic disable-next-line: assign-type-mismatch
|
---@diagnostic disable-next-line: assign-type-mismatch
|
||||||
if os.time({ year = year, month = month, day = day, hour = hour, min = minute }) < os.time() then
|
if os.time({ year = year, month = month, day = day, hour = hour, min = minute }) < os.time() then
|
||||||
year = year and year + 1 or currentDate.year + 1
|
year = year and year + 1 or currentDate.year + 1
|
||||||
@ -242,40 +354,50 @@ function OxTask:scheduleTask()
|
|||||||
local sleep = runAt - currentTime
|
local sleep = runAt - currentTime
|
||||||
|
|
||||||
if sleep < 0 then
|
if sleep < 0 then
|
||||||
return self:stop(self.debug and ('scheduled time expired %s seconds ago'):format(-sleep))
|
if not self.maxDelay or -sleep > self.maxDelay then
|
||||||
|
return self:stop(self.debug and ('scheduled time expired %s seconds ago'):format(-sleep))
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.debug then
|
||||||
|
lib.print.debug(('Task %s is %s seconds overdue, executing now due to maxDelay=%s'):format(
|
||||||
|
self.id,
|
||||||
|
-sleep,
|
||||||
|
self.maxDelay
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
sleep = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
local timeAsString = self:getTimeAsString(runAt)
|
local timeAsString = self:getTimeAsString(runAt)
|
||||||
|
|
||||||
if self.debug then
|
if self.debug then
|
||||||
print(('(%s) task %s will run in %d seconds (%0.2f minutes / %0.2f hours)'):format(timeAsString, self.id, sleep,
|
lib.print.debug(('(%s) task %s will run in %d seconds (%0.2f minutes / %0.2f hours)'):format(timeAsString, self.id, sleep,
|
||||||
sleep / 60,
|
sleep / 60,
|
||||||
sleep / 60 / 60))
|
sleep / 60 / 60))
|
||||||
end
|
end
|
||||||
|
|
||||||
if sleep > 0 then
|
if sleep > 0 then
|
||||||
Wait(sleep * 1000)
|
Wait(sleep * 1000)
|
||||||
else -- will this even happen?
|
else
|
||||||
Wait(1000)
|
Wait(0)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.isActive then
|
if self.isActive then
|
||||||
if self.debug then
|
if self.debug then
|
||||||
print(('(%s) running task %s'):format(timeAsString, self.id))
|
lib.print.debug(('(%s) running task %s'):format(timeAsString, self.id))
|
||||||
end
|
end
|
||||||
|
|
||||||
Citizen.CreateThreadNow(function()
|
Citizen.CreateThreadNow(function()
|
||||||
self:job(currentDate)
|
self:job(currentDate)
|
||||||
|
self.lastRun = os.time()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Wait(30000)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Start an inactive task.
|
|
||||||
function OxTask:run()
|
function OxTask:run()
|
||||||
if self.isActive then return end
|
if self.isActive then return end
|
||||||
|
|
||||||
@ -291,58 +413,20 @@ function OxTask:stop(msg)
|
|||||||
|
|
||||||
if self.debug then
|
if self.debug then
|
||||||
if msg then
|
if msg then
|
||||||
return print(('stopping task %s (%s)'):format(self.id, msg))
|
return lib.print.debug(('stopping task %s (%s)'):format(self.id, msg))
|
||||||
end
|
end
|
||||||
|
|
||||||
print(('stopping task %s'):format(self.id))
|
lib.print.debug(('stopping task %s'):format(self.id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param value string
|
|
||||||
---@return number | string | nil
|
|
||||||
local function parseCron(value, unit)
|
|
||||||
if not value or value == '*' then return end
|
|
||||||
|
|
||||||
local num = tonumber(value)
|
|
||||||
|
|
||||||
if num then return num end
|
|
||||||
|
|
||||||
if unit == 'wday' then
|
|
||||||
if value == 'sun' then return 1 end
|
|
||||||
if value == 'mon' then return 2 end
|
|
||||||
if value == 'tue' then return 3 end
|
|
||||||
if value == 'wed' then return 4 end
|
|
||||||
if value == 'thu' then return 5 end
|
|
||||||
if value == 'fri' then return 6 end
|
|
||||||
if value == 'sat' then return 7 end
|
|
||||||
end
|
|
||||||
|
|
||||||
if unit == 'month' then
|
|
||||||
if value == 'jan' then return 1 end
|
|
||||||
if value == 'feb' then return 2 end
|
|
||||||
if value == 'mar' then return 3 end
|
|
||||||
if value == 'apr' then return 4 end
|
|
||||||
if value == 'may' then return 5 end
|
|
||||||
if value == 'jun' then return 6 end
|
|
||||||
if value == 'jul' then return 7 end
|
|
||||||
if value == 'aug' then return 8 end
|
|
||||||
if value == 'sep' then return 9 end
|
|
||||||
if value == 'oct' then return 10 end
|
|
||||||
if value == 'nov' then return 11 end
|
|
||||||
if value == 'dec' then return 12 end
|
|
||||||
end
|
|
||||||
|
|
||||||
if getTimeUnit(value, unit) then return value end
|
|
||||||
|
|
||||||
error(("^1invalid cron expression. '%s' is not supported for %s^0"):format(value, unit), 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param expression string A cron expression such as `* * * * *` representing minute, hour, day, month, and day of the week.
|
---@param expression string A cron expression such as `* * * * *` representing minute, hour, day, month, and day of the week.
|
||||||
---@param job fun(task: OxTask, date: osdate)
|
---@param job fun(task: OxTask, date: osdate)
|
||||||
---@param options? { debug?: boolean }
|
---@param options? { debug?: boolean }
|
||||||
---Creates a new [cronjob](https://en.wikipedia.org/wiki/Cron), scheduling a task to run at fixed times or intervals.
|
---Creates a new [cronjob](https://en.wikipedia.org/wiki/Cron), scheduling a task to run at fixed times or intervals.
|
||||||
---Supports numbers, any value `*`, lists `1,2,3`, ranges `1-3`, and steps `*/4`.
|
---Supports numbers, any value `*`, lists `1,2,3`, ranges `1-3`, and steps `*/4`.
|
||||||
---Day of the week is a range of `1-7` starting from Sunday and allows short-names (i.e. sun, mon, tue).
|
---Day of the week is a range of `1-7` starting from Sunday and allows short-names (i.e. sun, mon, tue).
|
||||||
|
---@note maxDelay: Maximum allowed delay in seconds before skipping (0 to disable)
|
||||||
function lib.cron.new(expression, job, options)
|
function lib.cron.new(expression, job, options)
|
||||||
if not job or type(job) ~= 'function' then
|
if not job or type(job) ~= 'function' then
|
||||||
error(("expected job to have type 'function' (received %s)"):format(type(job)))
|
error(("expected job to have type 'function' (received %s)"):format(type(job)))
|
||||||
@ -360,6 +444,8 @@ function lib.cron.new(expression, job, options)
|
|||||||
task.weekday = parseCron(weekday, 'wday')
|
task.weekday = parseCron(weekday, 'wday')
|
||||||
task.id = #tasks + 1
|
task.id = #tasks + 1
|
||||||
task.job = job
|
task.job = job
|
||||||
|
task.lastRun = nil
|
||||||
|
task.maxDelay = task.maxDelay or 1
|
||||||
tasks[task.id] = task
|
tasks[task.id] = task
|
||||||
task:run()
|
task:run()
|
||||||
|
|
||||||
@ -370,7 +456,6 @@ end
|
|||||||
lib.cron.new('0 0 * * *', function()
|
lib.cron.new('0 0 * * *', function()
|
||||||
for i = 1, #tasks do
|
for i = 1, #tasks do
|
||||||
local task = tasks[i]
|
local task = tasks[i]
|
||||||
|
|
||||||
if not task.isActive then
|
if not task.isActive then
|
||||||
task:run()
|
task:run()
|
||||||
end
|
end
|
||||||
|
|||||||
87
resources/[core]/ox_lib/imports/dui/client.lua
Normal file
87
resources/[core]/ox_lib/imports/dui/client.lua
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
---@class DuiProperties
|
||||||
|
---@field url string
|
||||||
|
---@field width number
|
||||||
|
---@field height number
|
||||||
|
---@field debug? boolean
|
||||||
|
|
||||||
|
---@class Dui : OxClass
|
||||||
|
---@field private private { id: string, debug: boolean }
|
||||||
|
---@field url string
|
||||||
|
---@field duiObject number
|
||||||
|
---@field duiHandle string
|
||||||
|
---@field runtimeTxd number
|
||||||
|
---@field txdObject number
|
||||||
|
---@field dictName string
|
||||||
|
---@field txtName string
|
||||||
|
lib.dui = lib.class('Dui')
|
||||||
|
|
||||||
|
---@type table<string, Dui>
|
||||||
|
local duis = {}
|
||||||
|
|
||||||
|
local currentId = 0
|
||||||
|
|
||||||
|
---@param data DuiProperties
|
||||||
|
function lib.dui:constructor(data)
|
||||||
|
local time = GetGameTimer()
|
||||||
|
local id = ("%s_%s_%s"):format(cache.resource, time, currentId)
|
||||||
|
currentId = currentId + 1
|
||||||
|
local dictName = ('ox_lib_dui_dict_%s'):format(id)
|
||||||
|
local txtName = ('ox_lib_dui_txt_%s'):format(id)
|
||||||
|
local duiObject = CreateDui(data.url, data.width, data.height)
|
||||||
|
local duiHandle = GetDuiHandle(duiObject)
|
||||||
|
local runtimeTxd = CreateRuntimeTxd(dictName)
|
||||||
|
local txdObject = CreateRuntimeTextureFromDuiHandle(runtimeTxd, txtName, duiHandle)
|
||||||
|
self.private.id = id
|
||||||
|
self.private.debug = data.debug or false
|
||||||
|
self.url = data.url
|
||||||
|
self.duiObject = duiObject
|
||||||
|
self.duiHandle = duiHandle
|
||||||
|
self.runtimeTxd = runtimeTxd
|
||||||
|
self.txdObject = txdObject
|
||||||
|
self.dictName = dictName
|
||||||
|
self.txtName = txtName
|
||||||
|
duis[id] = self
|
||||||
|
|
||||||
|
if self.private.debug then
|
||||||
|
print(('Dui %s created'):format(id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.dui:remove()
|
||||||
|
SetDuiUrl(self.duiObject, 'about:blank')
|
||||||
|
DestroyDui(self.duiObject)
|
||||||
|
duis[self.private.id] = nil
|
||||||
|
|
||||||
|
if self.private.debug then
|
||||||
|
print(('Dui %s removed'):format(self.private.id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url string
|
||||||
|
function lib.dui:setUrl(url)
|
||||||
|
self.url = url
|
||||||
|
SetDuiUrl(self.duiObject, url)
|
||||||
|
|
||||||
|
if self.private.debug then
|
||||||
|
print(('Dui %s url set to %s'):format(self.private.id, url))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param message table
|
||||||
|
function lib.dui:sendMessage(message)
|
||||||
|
SendDuiMessage(self.duiObject, json.encode(message))
|
||||||
|
|
||||||
|
if self.private.debug then
|
||||||
|
print(('Dui %s message sent with data :'):format(self.private.id), json.encode(message, { indent = true }))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
AddEventHandler('onResourceStop', function(resourceName)
|
||||||
|
if cache.resource ~= resourceName then return end
|
||||||
|
|
||||||
|
for _, dui in pairs(duis) do
|
||||||
|
dui:remove()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return lib.dui
|
||||||
48
resources/[core]/ox_lib/imports/getRelativeCoords/shared.lua
Normal file
48
resources/[core]/ox_lib/imports/getRelativeCoords/shared.lua
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
local glm_sincos = require 'glm'.sincos --[[@as fun(n: number): number, number]]
|
||||||
|
local glm_rad = require 'glm'.rad --[[@as fun(n: number): number]]
|
||||||
|
|
||||||
|
---Get the relative coordinates based on heading/rotation and offset
|
||||||
|
---@overload fun(coords: vector3, heading: number, offset: vector3): vector3
|
||||||
|
---@overload fun(coords: vector4, offset: vector3): vector4
|
||||||
|
---@overload fun(coords: vector3, rotation: vector3, offset: vector3): vector3
|
||||||
|
function lib.getRelativeCoords(coords, rotation, offset)
|
||||||
|
if type(rotation) == 'vector3' and offset then
|
||||||
|
local pitch = glm_rad(rotation.x)
|
||||||
|
local roll = glm_rad(rotation.y)
|
||||||
|
local yaw = glm_rad(rotation.z)
|
||||||
|
|
||||||
|
local sp, cp = glm_sincos(pitch)
|
||||||
|
local sr, cr = glm_sincos(roll)
|
||||||
|
local sy, cy = glm_sincos(yaw)
|
||||||
|
|
||||||
|
local rotatedX = offset.x * (cy * cr) + offset.y * (cy * sr * sp - sy * cp) + offset.z * (cy * sr * cp + sy * sp)
|
||||||
|
local rotatedY = offset.x * (sy * cr) + offset.y * (sy * sr * sp + cy * cp) + offset.z * (sy * sr * cp - cy * sp)
|
||||||
|
local rotatedZ = offset.x * (-sr) + offset.y * (cr * sp) + offset.z * (cr * cp)
|
||||||
|
|
||||||
|
return vec3(
|
||||||
|
coords.x + rotatedX,
|
||||||
|
coords.y + rotatedY,
|
||||||
|
coords.z + rotatedZ
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
offset = offset or rotation
|
||||||
|
local x, y, z, w = coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or coords.w
|
||||||
|
|
||||||
|
local sin, cos = glm_sincos(glm_rad(w))
|
||||||
|
local relativeX = offset.x * cos - offset.y * sin
|
||||||
|
local relativeY = offset.x * sin + offset.y * cos
|
||||||
|
|
||||||
|
return coords.w and vec4(
|
||||||
|
x + relativeX,
|
||||||
|
y + relativeY,
|
||||||
|
z + offset.z,
|
||||||
|
w
|
||||||
|
) or vec3(
|
||||||
|
x + relativeX,
|
||||||
|
y + relativeY,
|
||||||
|
z + offset.z
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib.getRelativeCoords
|
||||||
194
resources/[core]/ox_lib/imports/grid/shared.lua
Normal file
194
resources/[core]/ox_lib/imports/grid/shared.lua
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
--[[
|
||||||
|
Based on PolyZone's grid system (https://github.com/mkafrin/PolyZone/blob/master/ComboZone.lua)
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019-2021 Michael Afrin
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local mapMinX = -3700
|
||||||
|
local mapMinY = -4400
|
||||||
|
local mapMaxX = 4500
|
||||||
|
local mapMaxY = 8000
|
||||||
|
local xDelta = (mapMaxX - mapMinX) / 34
|
||||||
|
local yDelta = (mapMaxY - mapMinY) / 50
|
||||||
|
local grid = {}
|
||||||
|
local lastCell = {}
|
||||||
|
local gridCache = {}
|
||||||
|
local entrySet = {}
|
||||||
|
|
||||||
|
lib.grid = {}
|
||||||
|
|
||||||
|
---@class GridEntry
|
||||||
|
---@field coords vector
|
||||||
|
---@field length? number
|
||||||
|
---@field width? number
|
||||||
|
---@field radius? number
|
||||||
|
---@field [string] any
|
||||||
|
|
||||||
|
---@param point vector
|
||||||
|
---@param length number
|
||||||
|
---@param width number
|
||||||
|
---@return number, number, number, number
|
||||||
|
local function getGridDimensions(point, length, width)
|
||||||
|
local minX = (point.x - width - mapMinX) // xDelta
|
||||||
|
local maxX = (point.x + width - mapMinX) // xDelta
|
||||||
|
local minY = (point.y - length - mapMinY) // yDelta
|
||||||
|
local maxY = (point.y + length - mapMinY) // yDelta
|
||||||
|
|
||||||
|
return minX, maxX, minY, maxY
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param point vector
|
||||||
|
---@return number, number
|
||||||
|
function lib.grid.getCellPosition(point)
|
||||||
|
local x = (point.x - mapMinX) // xDelta
|
||||||
|
local y = (point.y - mapMinY) // yDelta
|
||||||
|
|
||||||
|
return x, y
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param point vector
|
||||||
|
---@return GridEntry[]
|
||||||
|
function lib.grid.getCell(point)
|
||||||
|
local x, y = lib.grid.getCellPosition(point)
|
||||||
|
|
||||||
|
if lastCell.x ~= x or lastCell.y ~= y then
|
||||||
|
lastCell.x = x
|
||||||
|
lastCell.y = y
|
||||||
|
lastCell.cell = grid[y] and grid[y][x] or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return lastCell.cell
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param point vector
|
||||||
|
---@param filter? fun(entry: GridEntry): boolean
|
||||||
|
---@return Array<GridEntry>
|
||||||
|
function lib.grid.getNearbyEntries(point, filter)
|
||||||
|
local minX, maxX, minY, maxY = getGridDimensions(point, xDelta, yDelta)
|
||||||
|
|
||||||
|
if gridCache.filter == filter and
|
||||||
|
gridCache.minX == minX and
|
||||||
|
gridCache.maxX == maxX and
|
||||||
|
gridCache.minY == minY and
|
||||||
|
gridCache.maxY == maxY then
|
||||||
|
return gridCache.entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local entries = lib.array:new()
|
||||||
|
local n = 0
|
||||||
|
|
||||||
|
table.wipe(entrySet)
|
||||||
|
|
||||||
|
for y = minY, maxY do
|
||||||
|
local row = grid[y]
|
||||||
|
|
||||||
|
for x = minX, maxX do
|
||||||
|
local cell = row and row[x]
|
||||||
|
|
||||||
|
if cell then
|
||||||
|
for j = 1, #cell do
|
||||||
|
local entry = cell[j]
|
||||||
|
|
||||||
|
if not entrySet[entry] and (not filter or filter(entry)) then
|
||||||
|
n = n + 1
|
||||||
|
entrySet[entry] = true
|
||||||
|
entries[n] = entry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
gridCache.minX = minX
|
||||||
|
gridCache.maxX = maxX
|
||||||
|
gridCache.minY = minY
|
||||||
|
gridCache.maxY = maxY
|
||||||
|
gridCache.entries = entries
|
||||||
|
gridCache.filter = filter
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param entry { coords: vector, length?: number, width?: number, radius?: number, [string]: any }
|
||||||
|
function lib.grid.addEntry(entry)
|
||||||
|
entry.length = entry.length or entry.radius * 2
|
||||||
|
entry.width = entry.width or entry.radius * 2
|
||||||
|
local minX, maxX, minY, maxY = getGridDimensions(entry.coords, entry.length, entry.width)
|
||||||
|
|
||||||
|
for y = minY, maxY do
|
||||||
|
local row = grid[y] or {}
|
||||||
|
|
||||||
|
for x = minX, maxX do
|
||||||
|
local cell = row[x] or {}
|
||||||
|
|
||||||
|
cell[#cell + 1] = entry
|
||||||
|
row[x] = cell
|
||||||
|
end
|
||||||
|
|
||||||
|
grid[y] = row
|
||||||
|
|
||||||
|
table.wipe(gridCache)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param entry table A table that was added to the grid previously.
|
||||||
|
function lib.grid.removeEntry(entry)
|
||||||
|
local minX, maxX, minY, maxY = getGridDimensions(entry.coords, entry.length, entry.width)
|
||||||
|
local success = false
|
||||||
|
|
||||||
|
for y = minY, maxY do
|
||||||
|
local row = grid[y]
|
||||||
|
|
||||||
|
if not row then goto continue end
|
||||||
|
|
||||||
|
for x = minX, maxX do
|
||||||
|
local cell = row[x]
|
||||||
|
|
||||||
|
if cell then
|
||||||
|
for i = 1, #cell do
|
||||||
|
if cell[i] == entry then
|
||||||
|
table.remove(cell, i)
|
||||||
|
success = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #cell == 0 then
|
||||||
|
row[x] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not next(row) then
|
||||||
|
grid[y] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
|
||||||
|
table.wipe(gridCache)
|
||||||
|
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib.grid
|
||||||
@ -5,9 +5,6 @@ local defaultRotation = vector3(0, 0, 0)
|
|||||||
local defaultDirection = vector3(0, 0, 0)
|
local defaultDirection = vector3(0, 0, 0)
|
||||||
local defaultColor = { r = 255, g = 255, b = 255, a = 100 }
|
local defaultColor = { r = 255, g = 255, b = 255, a = 100 }
|
||||||
local defaultSize = { width = 2, height = 1 }
|
local defaultSize = { width = 2, height = 1 }
|
||||||
local defaultBobUpAndDown = false
|
|
||||||
local defaultFaceCamera = true
|
|
||||||
local defaultRotate = false
|
|
||||||
local defaultTextureDict = nil
|
local defaultTextureDict = nil
|
||||||
local defaultTextureName = nil
|
local defaultTextureName = nil
|
||||||
|
|
||||||
@ -152,9 +149,9 @@ function lib.marker.new(options)
|
|||||||
self.height = options.height or defaultSize.height
|
self.height = options.height or defaultSize.height
|
||||||
self.rotation = options.rotation or defaultRotation
|
self.rotation = options.rotation or defaultRotation
|
||||||
self.direction = options.direction or defaultDirection
|
self.direction = options.direction or defaultDirection
|
||||||
self.bobUpAndDown = options.bobUpAndDown or defaultBobUpAndDown
|
self.bobUpAndDown = type(options.bobUpAndDown) == 'boolean' and options.bobUpAndDown
|
||||||
self.faceCamera = options.faceCamera or defaultFaceCamera
|
self.faceCamera = type(options.faceCamera) ~= 'boolean' or options.faceCamera
|
||||||
self.rotate = options.rotate or defaultRotate
|
self.rotate = type(options.rotate) == 'boolean' and options.rotate
|
||||||
self.textureDict = options.textureDict or defaultTextureDict
|
self.textureDict = options.textureDict or defaultTextureDict
|
||||||
self.textureName = options.textureName or defaultTextureName
|
self.textureName = options.textureName or defaultTextureName
|
||||||
self.draw = drawMarker
|
self.draw = drawMarker
|
||||||
|
|||||||
@ -26,74 +26,93 @@ local function removePoint(self)
|
|||||||
closestPoint = nil
|
closestPoint = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
points[self.id] = nil
|
lib.grid.removeEntry(self)
|
||||||
|
|
||||||
|
points[self.id] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
while true do
|
while true do
|
||||||
if nearbyCount ~= 0 then
|
if nearbyCount ~= 0 then
|
||||||
table.wipe(nearbyPoints)
|
table.wipe(nearbyPoints)
|
||||||
nearbyCount = 0
|
nearbyCount = 0
|
||||||
end
|
|
||||||
|
|
||||||
local coords = GetEntityCoords(cache.ped)
|
|
||||||
cache.coords = coords
|
|
||||||
|
|
||||||
if closestPoint and #(coords - closestPoint.coords) > closestPoint.distance then
|
|
||||||
closestPoint = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, point in pairs(points) do
|
local coords = GetEntityCoords(cache.ped)
|
||||||
local distance = #(coords - point.coords)
|
local newPoints = lib.grid.getNearbyEntries(coords, function(entry) return entry.remove == removePoint end) --[[@as CPoint[] ]]
|
||||||
|
local cellX, cellY = lib.grid.getCellPosition(coords)
|
||||||
|
cache.coords = coords
|
||||||
|
closestPoint = nil
|
||||||
|
|
||||||
if distance <= point.distance then
|
if cellX ~= cache.lastCellX or cellY ~= cache.lastCellY then
|
||||||
point.currentDistance = distance
|
for i = 1, #nearbyPoints do
|
||||||
|
local point = nearbyPoints[i]
|
||||||
|
|
||||||
if closestPoint then
|
if point.inside then
|
||||||
if distance < closestPoint.currentDistance then
|
local distance = #(coords - point.coords)
|
||||||
closestPoint.isClosest = nil
|
|
||||||
point.isClosest = true
|
if distance > point.radius then
|
||||||
closestPoint = point
|
if point.onExit then point:onExit() end
|
||||||
|
|
||||||
|
point.inside = nil
|
||||||
|
point.currentDistance = nil
|
||||||
end
|
end
|
||||||
elseif distance < point.distance then
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
cache.lastCellX = cellX
|
||||||
|
cache.lastCellY = cellY
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #newPoints do
|
||||||
|
local point = newPoints[i]
|
||||||
|
local distance = #(coords - point.coords)
|
||||||
|
|
||||||
|
if distance <= point.radius then
|
||||||
|
point.currentDistance = distance
|
||||||
|
|
||||||
|
if not closestPoint or distance < (closestPoint.currentDistance or point.radius) then
|
||||||
|
if closestPoint then closestPoint.isClosest = nil end
|
||||||
|
|
||||||
point.isClosest = true
|
point.isClosest = true
|
||||||
closestPoint = point
|
closestPoint = point
|
||||||
end
|
end
|
||||||
|
|
||||||
if point.nearby then
|
if point.nearby then
|
||||||
nearbyCount += 1
|
nearbyCount += 1
|
||||||
nearbyPoints[nearbyCount] = point
|
nearbyPoints[nearbyCount] = point
|
||||||
end
|
end
|
||||||
|
|
||||||
if point.onEnter and not point.inside then
|
if point.onEnter and not point.inside then
|
||||||
point.inside = true
|
point.inside = true
|
||||||
point:onEnter()
|
point:onEnter()
|
||||||
end
|
end
|
||||||
elseif point.currentDistance then
|
elseif point.currentDistance then
|
||||||
if point.onExit then point:onExit() end
|
if point.onExit then point:onExit() end
|
||||||
point.inside = nil
|
|
||||||
point.currentDistance = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not tick then
|
point.inside = nil
|
||||||
if nearbyCount ~= 0 then
|
point.currentDistance = nil
|
||||||
tick = SetInterval(function()
|
end
|
||||||
for i = 1, nearbyCount do
|
end
|
||||||
|
|
||||||
|
if not tick then
|
||||||
|
if nearbyCount ~= 0 then
|
||||||
|
tick = SetInterval(function()
|
||||||
|
for i = nearbyCount, 1, -1 do
|
||||||
local point = nearbyPoints[i]
|
local point = nearbyPoints[i]
|
||||||
|
|
||||||
if point then
|
if point and point.nearby then
|
||||||
point:nearby()
|
point:nearby()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
elseif nearbyCount == 0 then
|
elseif nearbyCount == 0 then
|
||||||
tick = ClearInterval(tick)
|
tick = ClearInterval(tick)
|
||||||
end
|
end
|
||||||
|
|
||||||
Wait(300)
|
Wait(300)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function toVector(coords)
|
local function toVector(coords)
|
||||||
@ -110,50 +129,52 @@ local function toVector(coords)
|
|||||||
return coords
|
return coords
|
||||||
end
|
end
|
||||||
|
|
||||||
lib.points = {
|
lib.points = {}
|
||||||
---@return CPoint
|
|
||||||
---@overload fun(data: PointProperties): CPoint
|
|
||||||
---@overload fun(coords: vector3, distance: number, data?: PointProperties): CPoint
|
|
||||||
new = function(...)
|
|
||||||
local args = {...}
|
|
||||||
local id = #points + 1
|
|
||||||
local self
|
|
||||||
|
|
||||||
-- Support sending a single argument containing point data
|
---@return CPoint
|
||||||
if type(args[1]) == 'table' then
|
---@overload fun(data: PointProperties): CPoint
|
||||||
self = args[1]
|
---@overload fun(coords: vector3, distance: number, data?: PointProperties): CPoint
|
||||||
self.id = id
|
function lib.points.new(...)
|
||||||
self.remove = removePoint
|
local args = { ... }
|
||||||
else
|
local id = #points + 1
|
||||||
-- Backwards compatibility for original implementation (args: coords, distance, data)
|
local self
|
||||||
self = {
|
|
||||||
id = id,
|
|
||||||
coords = args[1],
|
|
||||||
remove = removePoint,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
self.coords = toVector(self.coords)
|
-- Support sending a single argument containing point data
|
||||||
self.distance = self.distance or args[2]
|
if type(args[1]) == 'table' then
|
||||||
|
self = args[1]
|
||||||
|
self.id = id
|
||||||
|
self.remove = removePoint
|
||||||
|
else
|
||||||
|
-- Backwards compatibility for original implementation (args: coords, distance, data)
|
||||||
|
self = {
|
||||||
|
id = id,
|
||||||
|
coords = args[1],
|
||||||
|
remove = removePoint,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
if args[3] then
|
self.coords = toVector(self.coords)
|
||||||
for k, v in pairs(args[3]) do
|
self.distance = self.distance or args[2]
|
||||||
self[k] = v
|
self.radius = self.distance
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
points[id] = self
|
if args[3] then
|
||||||
|
for k, v in pairs(args[3]) do
|
||||||
|
self[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return self
|
lib.grid.addEntry(self)
|
||||||
end,
|
points[id] = self
|
||||||
|
|
||||||
getAllPoints = function() return points end,
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
getNearbyPoints = function() return nearbyPoints end,
|
function lib.points.getAllPoints() return points end
|
||||||
|
|
||||||
---@return CPoint?
|
function lib.points.getNearbyPoints() return nearbyPoints end
|
||||||
getClosestPoint = function() return closestPoint end,
|
|
||||||
}
|
---@return CPoint?
|
||||||
|
function lib.points.getClosestPoint() return closestPoint end
|
||||||
|
|
||||||
---@deprecated
|
---@deprecated
|
||||||
lib.points.closest = lib.points.getClosestPoint
|
lib.points.closest = lib.points.getClosestPoint
|
||||||
|
|||||||
@ -14,8 +14,12 @@ local levelPrefixes = {
|
|||||||
'^4[VERBOSE]',
|
'^4[VERBOSE]',
|
||||||
'^6[DEBUG]',
|
'^6[DEBUG]',
|
||||||
}
|
}
|
||||||
|
local convarGlobal = 'ox:printlevel'
|
||||||
local resourcePrintLevel = printLevel[GetConvar('ox:printlevel:' .. cache.resource, GetConvar('ox:printlevel', 'info'))]
|
local convarResource = 'ox:printlevel:' .. cache.resource
|
||||||
|
local function getPrintLevelFromConvar()
|
||||||
|
return printLevel[GetConvar(convarResource, GetConvar(convarGlobal, 'info'))]
|
||||||
|
end
|
||||||
|
local resourcePrintLevel = getPrintLevelFromConvar()
|
||||||
local template = ('^5[%s] %%s %%s^7'):format(cache.resource)
|
local template = ('^5[%s] %%s %%s^7'):format(cache.resource)
|
||||||
local function handleException(reason, value)
|
local function handleException(reason, value)
|
||||||
if type(value) == 'function' then return tostring(value) end
|
if type(value) == 'function' then return tostring(value) end
|
||||||
@ -48,4 +52,14 @@ lib.print = {
|
|||||||
debug = function(...) libPrint(printLevel.debug, ...) end,
|
debug = function(...) libPrint(printLevel.debug, ...) end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Update the print level when the convar changes
|
||||||
|
if (AddConvarChangeListener) then
|
||||||
|
AddConvarChangeListener('ox:printlevel*', function(convarName, reserved)
|
||||||
|
if (convarName ~= convarResource and convarName ~= convarGlobal) then return end
|
||||||
|
resourcePrintLevel = getPrintLevelFromConvar()
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
libPrint(printLevel.verbose, 'Convar change listener not available, print level will not update dynamically.')
|
||||||
|
end
|
||||||
|
|
||||||
return lib.print
|
return lib.print
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
function lib.requestAudioBank(audioBank, timeout)
|
function lib.requestAudioBank(audioBank, timeout)
|
||||||
return lib.waitFor(function()
|
return lib.waitFor(function()
|
||||||
if RequestScriptAudioBank(audioBank, false) then return audioBank end
|
if RequestScriptAudioBank(audioBank, false) then return audioBank end
|
||||||
end, ("failed to load audiobank '%s'"):format(audioBank), timeout or 500)
|
end, ("failed to load audiobank '%s' - this may be caused by\n- too many loaded assets\n- oversized, invalid, or corrupted assets"):format(audioBank), timeout or 30000)
|
||||||
end
|
end
|
||||||
|
|
||||||
return lib.requestAudioBank
|
return lib.requestAudioBank
|
||||||
|
|||||||
224
resources/[core]/ox_lib/imports/scaleform/client.lua
Normal file
224
resources/[core]/ox_lib/imports/scaleform/client.lua
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
---@class renderTargetTable
|
||||||
|
---@field name string
|
||||||
|
---@field model string | number
|
||||||
|
|
||||||
|
---@class detailsTable
|
||||||
|
---@field name string
|
||||||
|
---@field fullScreen? boolean
|
||||||
|
---@field x? number
|
||||||
|
---@field y? number
|
||||||
|
---@field width? number
|
||||||
|
---@field height? number
|
||||||
|
---@field renderTarget? renderTargetTable
|
||||||
|
|
||||||
|
---@class Scaleform : OxClass
|
||||||
|
---@field scaleform number
|
||||||
|
---@field draw boolean
|
||||||
|
---@field target number
|
||||||
|
---@field targetName string
|
||||||
|
---@field sfHandle? number
|
||||||
|
---@field fullScreen boolean
|
||||||
|
---@field private private { isDrawing: boolean }
|
||||||
|
lib.scaleform = lib.class('Scaleform')
|
||||||
|
|
||||||
|
--- Converts the arguments into data types usable by scaleform
|
||||||
|
---@param argsTable (number | string | boolean)[]
|
||||||
|
local function convertArgs(argsTable)
|
||||||
|
for i = 1, #argsTable do
|
||||||
|
local arg = argsTable[i]
|
||||||
|
local argType = type(arg)
|
||||||
|
|
||||||
|
if argType == 'string' then
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(arg)
|
||||||
|
elseif argType == 'number' then
|
||||||
|
if math.type(arg) == 'integer' then
|
||||||
|
ScaleformMovieMethodAddParamInt(arg)
|
||||||
|
else
|
||||||
|
ScaleformMovieMethodAddParamFloat(arg)
|
||||||
|
end
|
||||||
|
elseif argType == 'boolean' then
|
||||||
|
ScaleformMovieMethodAddParamBool(arg)
|
||||||
|
else
|
||||||
|
error(('Unsupported Parameter type [%s]'):format(argType))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param expectedType 'boolean' | 'integer' | 'string'
|
||||||
|
---@return boolean | integer | string
|
||||||
|
local function retrieveReturnValue(expectedType)
|
||||||
|
local result = EndScaleformMovieMethodReturnValue()
|
||||||
|
|
||||||
|
lib.waitFor(function()
|
||||||
|
if IsScaleformMovieMethodReturnValueReady(result) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end, "Failed to retrieve return value", 1000)
|
||||||
|
|
||||||
|
if expectedType == "integer" then
|
||||||
|
return GetScaleformMovieMethodReturnValueInt(result)
|
||||||
|
elseif expectedType == "boolean" then
|
||||||
|
return GetScaleformMovieMethodReturnValueBool(result)
|
||||||
|
else
|
||||||
|
return GetScaleformMovieMethodReturnValueString(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param details detailsTable | string
|
||||||
|
---@return nil
|
||||||
|
function lib.scaleform:constructor(details)
|
||||||
|
details = type(details) == 'table' and details or { name = details }
|
||||||
|
|
||||||
|
local scaleform = lib.requestScaleformMovie(details.name)
|
||||||
|
|
||||||
|
self.sfHandle = scaleform
|
||||||
|
self.private.isDrawing = false
|
||||||
|
|
||||||
|
self.fullScreen = details.fullScreen or false
|
||||||
|
self.x = details.x or 0
|
||||||
|
self.y = details.y or 0
|
||||||
|
self.width = details.width or 0
|
||||||
|
self.height = details.height or 0
|
||||||
|
|
||||||
|
if details.renderTarget then
|
||||||
|
self:setRenderTarget(details.renderTarget.name, details.renderTarget.model)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param args? (number | string | boolean)[]
|
||||||
|
---@param returnValue? string
|
||||||
|
---@return any
|
||||||
|
function lib.scaleform:callMethod(name, args, returnValue)
|
||||||
|
if not self.sfHandle then
|
||||||
|
return error("attempted to call method with invalid scaleform handle")
|
||||||
|
end
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.sfHandle, name)
|
||||||
|
|
||||||
|
if args and type(args) == 'table' then
|
||||||
|
convertArgs(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
if returnValue then
|
||||||
|
return retrieveReturnValue(returnValue)
|
||||||
|
end
|
||||||
|
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param isFullscreen boolean
|
||||||
|
---@return nil
|
||||||
|
function lib.scaleform:setFullScreen(isFullscreen)
|
||||||
|
self.fullScreen = isFullscreen
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param width number
|
||||||
|
---@param height number
|
||||||
|
---@return nil
|
||||||
|
function lib.scaleform:setProperties(x, y, width, height)
|
||||||
|
if self.fullScreen then
|
||||||
|
lib.print.info('Cannot set properties when full screen is enabled')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param model string|number
|
||||||
|
---@return nil
|
||||||
|
function lib.scaleform:setRenderTarget(name, model)
|
||||||
|
if self.target then
|
||||||
|
ReleaseNamedRendertarget(self.targetName)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(model) == 'string' then
|
||||||
|
model = joaat(model)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not IsNamedRendertargetRegistered(name) then
|
||||||
|
RegisterNamedRendertarget(name, false)
|
||||||
|
|
||||||
|
if not IsNamedRendertargetLinked(model) then
|
||||||
|
LinkNamedRendertarget(model)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.target = GetNamedRendertargetRenderId(name)
|
||||||
|
self.targetName = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.scaleform:isDrawing()
|
||||||
|
return self.private.isDrawing
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.scaleform:draw()
|
||||||
|
if self.target then
|
||||||
|
SetTextRenderId(self.target)
|
||||||
|
SetScriptGfxDrawOrder(4)
|
||||||
|
SetScriptGfxDrawBehindPausemenu(true)
|
||||||
|
SetScaleformFitRendertarget(self.sfHandle, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.fullScreen then
|
||||||
|
DrawScaleformMovieFullscreen(self.sfHandle, 255, 255, 255, 255, 0)
|
||||||
|
else
|
||||||
|
if not self.x or not self.y or not self.width or not self.height then
|
||||||
|
error('attempted to draw scaleform without setting properties')
|
||||||
|
else
|
||||||
|
DrawScaleformMovie(self.sfHandle, self.x, self.y, self.width, self.height, 255, 255, 255, 255, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.target then
|
||||||
|
SetTextRenderId(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.scaleform:startDrawing()
|
||||||
|
if self.private.isDrawing then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.private.isDrawing = true
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
while self:isDrawing() do
|
||||||
|
self:draw()
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
|
function lib.scaleform:stopDrawing()
|
||||||
|
if not self.private.isDrawing then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.private.isDrawing = false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
|
function lib.scaleform:dispose()
|
||||||
|
if self.sfHandle then
|
||||||
|
SetScaleformMovieAsNoLongerNeeded(self.sfHandle)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.target then
|
||||||
|
ReleaseNamedRendertarget(self.targetName)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.sfHandle = nil
|
||||||
|
self.target = nil
|
||||||
|
self.private.isDrawing = false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return Scaleform
|
||||||
|
return lib.scaleform
|
||||||
@ -12,12 +12,10 @@ function lib.streamingRequest(request, hasLoaded, assetType, asset, timeout, ...
|
|||||||
|
|
||||||
request(asset, ...)
|
request(asset, ...)
|
||||||
|
|
||||||
-- i hate fivem developers
|
|
||||||
lib.print.verbose(("Loading %s '%s' - remember to release it when done."):format(assetType, asset))
|
|
||||||
|
|
||||||
return lib.waitFor(function()
|
return lib.waitFor(function()
|
||||||
if hasLoaded(asset) then return asset end
|
if hasLoaded(asset) then return asset end
|
||||||
end, ("failed to load %s '%s' - this is likely caused by unreleased assets"):format(assetType, asset), timeout or 10000)
|
end, ("failed to load %s '%s' - this may be caused by\n- too many loaded assets\n- oversized, invalid, or corrupted assets"):format(assetType, asset),
|
||||||
|
timeout or 30000)
|
||||||
end
|
end
|
||||||
|
|
||||||
return lib.streamingRequest
|
return lib.streamingRequest
|
||||||
|
|||||||
@ -9,24 +9,29 @@ local pairs = pairs
|
|||||||
---@return boolean
|
---@return boolean
|
||||||
---Checks if tbl contains the given values. Only intended for simple values and unnested tables.
|
---Checks if tbl contains the given values. Only intended for simple values and unnested tables.
|
||||||
local function contains(tbl, value)
|
local function contains(tbl, value)
|
||||||
if type(value) ~= 'table' then
|
if type(value) ~= 'table' then
|
||||||
for _, v in pairs(tbl) do
|
for _, v in pairs(tbl) do
|
||||||
if v == value then return true end
|
if v == value then
|
||||||
end
|
return true
|
||||||
else
|
end
|
||||||
local matched_values = 0
|
end
|
||||||
local values = 0
|
|
||||||
for _, v1 in pairs(value) do
|
|
||||||
values += 1
|
|
||||||
|
|
||||||
for _, v2 in pairs(tbl) do
|
return false
|
||||||
if v1 == v2 then matched_values += 1 end
|
else
|
||||||
end
|
local set = {}
|
||||||
end
|
|
||||||
if matched_values == values then return true end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
for _, v in pairs(tbl) do
|
||||||
|
set[v] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, v in pairs(value) do
|
||||||
|
if not set[v] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param t1 any
|
---@param t1 any
|
||||||
@ -34,22 +39,28 @@ end
|
|||||||
---@return boolean
|
---@return boolean
|
||||||
---Compares if two values are equal, iterating over tables and matching both keys and values.
|
---Compares if two values are equal, iterating over tables and matching both keys and values.
|
||||||
local function table_matches(t1, t2)
|
local function table_matches(t1, t2)
|
||||||
local type1, type2 = type(t1), type(t2)
|
local tabletype1 = table.type(t1)
|
||||||
|
|
||||||
if type1 ~= type2 then return false end
|
if not tabletype1 then return t1 == t2 end
|
||||||
if type1 ~= 'table' and type2 ~= 'table' then return t1 == t2 end
|
|
||||||
|
|
||||||
for k1,v1 in pairs(t1) do
|
if tabletype1 ~= table.type(t2) or (tabletype1 == 'array' and #t1 ~= #t2) then
|
||||||
local v2 = t2[k1]
|
return false
|
||||||
if v2 == nil or not table_matches(v1,v2) then return false end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
for k2,v2 in pairs(t2) do
|
for k, v1 in pairs(t1) do
|
||||||
local v1 = t1[k2]
|
local v2 = t2[k]
|
||||||
if v1 == nil or not table_matches(v1,v2) then return false end
|
if v2 == nil or not table_matches(v1, v2) then
|
||||||
end
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return true
|
for k in pairs(t2) do
|
||||||
|
if t1[k] == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@generic T
|
---@generic T
|
||||||
@ -57,15 +68,15 @@ end
|
|||||||
---@return T
|
---@return T
|
||||||
---Recursively clones a table to ensure no table references.
|
---Recursively clones a table to ensure no table references.
|
||||||
local function table_deepclone(tbl)
|
local function table_deepclone(tbl)
|
||||||
tbl = table.clone(tbl)
|
tbl = table.clone(tbl)
|
||||||
|
|
||||||
for k, v in pairs(tbl) do
|
for k, v in pairs(tbl) do
|
||||||
if type(v) == 'table' then
|
if type(v) == 'table' then
|
||||||
tbl[k] = table_deepclone(v)
|
tbl[k] = table_deepclone(v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return tbl
|
return tbl
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param t1 table
|
---@param t1 table
|
||||||
@ -74,27 +85,41 @@ end
|
|||||||
---@return table
|
---@return table
|
||||||
---Merges two tables together. Defaults to adding duplicate keys together if they are numbers, otherwise they are overriden.
|
---Merges two tables together. Defaults to adding duplicate keys together if they are numbers, otherwise they are overriden.
|
||||||
local function table_merge(t1, t2, addDuplicateNumbers)
|
local function table_merge(t1, t2, addDuplicateNumbers)
|
||||||
if addDuplicateNumbers == nil then addDuplicateNumbers = true end
|
addDuplicateNumbers = addDuplicateNumbers == nil or addDuplicateNumbers
|
||||||
for k, v in pairs(t2) do
|
for k, v2 in pairs(t2) do
|
||||||
local type1 = type(t1[k])
|
local v1 = t1[k]
|
||||||
local type2 = type(v)
|
local type1 = type(v1)
|
||||||
|
local type2 = type(v2)
|
||||||
|
|
||||||
if type1 == 'table' and type2 == 'table' then
|
if type1 == 'table' and type2 == 'table' then
|
||||||
table_merge(t1[k], v, addDuplicateNumbers)
|
table_merge(v1, v2, addDuplicateNumbers)
|
||||||
elseif addDuplicateNumbers and (type1 == 'number' and type2 == 'number') then
|
elseif addDuplicateNumbers and (type1 == 'number' and type2 == 'number') then
|
||||||
t1[k] += v
|
t1[k] = v1 + v2
|
||||||
else
|
else
|
||||||
t1[k] = v
|
t1[k] = v2
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return t1
|
return t1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param tbl table
|
||||||
|
---@return table
|
||||||
|
---Shuffles the elements of a table randomly using the Fisher-Yates algorithm.
|
||||||
|
local function shuffle(tbl)
|
||||||
|
local len = #tbl
|
||||||
|
for i = len, 2, -1 do
|
||||||
|
local j = math.random(i)
|
||||||
|
tbl[i], tbl[j] = tbl[j], tbl[i]
|
||||||
|
end
|
||||||
|
return tbl
|
||||||
|
end
|
||||||
|
|
||||||
table.contains = contains
|
table.contains = contains
|
||||||
table.matches = table_matches
|
table.matches = table_matches
|
||||||
table.deepclone = table_deepclone
|
table.deepclone = table_deepclone
|
||||||
table.merge = table_merge
|
table.merge = table_merge
|
||||||
|
table.shuffle = shuffle
|
||||||
|
|
||||||
local frozenNewIndex = function(self) error(('cannot set values on a frozen table (%s)'):format(self), 2) end
|
local frozenNewIndex = function(self) error(('cannot set values on a frozen table (%s)'):format(self), 2) end
|
||||||
local _rawset = rawset
|
local _rawset = rawset
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
---@class TimerPrivateProps
|
---@class TimerPrivateProps
|
||||||
---@field initialTime number the initial duration of the timer.
|
---@field initialTime number the initial duration of the timer.
|
||||||
---@field onEnd? fun() cb function triggered when the timer finishes
|
|
||||||
---@field async? boolean wether the timer should run asynchronously or not
|
---@field async? boolean wether the timer should run asynchronously or not
|
||||||
---@field startTime number the gametimer stamp of when the timer starts. changes when paused and played
|
---@field startTime number the gametimer stamp of when the timer starts. changes when paused and played
|
||||||
---@field triggerOnEnd boolean set in the forceEnd method using the optional param. wether or not the onEnd function is triggered when force ending the timer early
|
---@field triggerOnEnd boolean set in the forceEnd method using the optional param. wether or not the onEnd function is triggered when force ending the timer early
|
||||||
@ -10,6 +9,7 @@
|
|||||||
---@class OxTimer : OxClass
|
---@class OxTimer : OxClass
|
||||||
---@field private private TimerPrivateProps
|
---@field private private TimerPrivateProps
|
||||||
---@field start fun(self: self, async?: boolean) starts the timer
|
---@field start fun(self: self, async?: boolean) starts the timer
|
||||||
|
---@field onEnd? fun() cb function triggered when the timer finishes
|
||||||
---@field forceEnd fun(self: self, triggerOnEnd: boolean) end timer early and optionally trigger the onEnd function still
|
---@field forceEnd fun(self: self, triggerOnEnd: boolean) end timer early and optionally trigger the onEnd function still
|
||||||
---@field isPaused fun(self: self): boolean returns wether the timer is paused or not
|
---@field isPaused fun(self: self): boolean returns wether the timer is paused or not
|
||||||
---@field pause fun(self: self) pauses the timer until play method is called
|
---@field pause fun(self: self) pauses the timer until play method is called
|
||||||
@ -26,58 +26,54 @@ function timer:constructor(time, onEnd, async)
|
|||||||
assert(onEnd == nil or type(onEnd) == "function", "onEnd must be a function or nil")
|
assert(onEnd == nil or type(onEnd) == "function", "onEnd must be a function or nil")
|
||||||
assert(type(async) == "boolean" or async == nil, "async must be a boolean or nil")
|
assert(type(async) == "boolean" or async == nil, "async must be a boolean or nil")
|
||||||
|
|
||||||
|
self.onEnd = onEnd
|
||||||
self.private.initialTime = time
|
self.private.initialTime = time
|
||||||
self.private.currentTimeLeft = time
|
self.private.currentTimeLeft = time
|
||||||
self.private.startTime = 0
|
self.private.startTime = 0
|
||||||
self.private.paused = false
|
self.private.paused = false
|
||||||
self.private.onEnd = onEnd
|
|
||||||
self.private.triggerOnEnd = true
|
self.private.triggerOnEnd = true
|
||||||
|
|
||||||
self:start(async)
|
self:start(async)
|
||||||
end
|
end
|
||||||
|
|
||||||
function timer:start(async)
|
---@protected
|
||||||
if self.private.startTime > 0 then return end
|
function timer:run()
|
||||||
|
while self:isPaused() or self:getTimeLeft('ms') > 0 do
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
|
||||||
self.private.startTime = GetGameTimer()
|
if self.private.triggerOnEnd then
|
||||||
|
|
||||||
local function tick()
|
|
||||||
while self:getTimeLeft('ms') > 0 do
|
|
||||||
while self:isPaused() do
|
|
||||||
Wait(0)
|
|
||||||
end
|
|
||||||
Wait(0)
|
|
||||||
end
|
|
||||||
self:onEnd()
|
self:onEnd()
|
||||||
end
|
end
|
||||||
|
|
||||||
if async then
|
self.private.triggerOnEnd = true
|
||||||
Citizen.CreateThreadNow(function()
|
|
||||||
tick()
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
tick()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function timer:onEnd()
|
function timer:start(async)
|
||||||
if self:getTimeLeft('ms') > 0 then return end
|
if self.private.startTime > 0 then error('Cannot start a timer that is already running') end
|
||||||
|
|
||||||
if self.private.triggerOnEnd and self.private.onEnd then
|
self.private.startTime = GetGameTimer()
|
||||||
self.private:onEnd()
|
|
||||||
end
|
if not async then return self:run() end
|
||||||
|
|
||||||
|
Citizen.CreateThreadNow(function()
|
||||||
|
self:run()
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function timer:forceEnd(triggerOnEnd)
|
function timer:forceEnd(triggerOnEnd)
|
||||||
if self:getTimeLeft('ms') <= 0 then return end
|
if self:getTimeLeft('ms') <= 0 then return end
|
||||||
self.private.triggerOnEnd = triggerOnEnd
|
|
||||||
self.private.paused = false
|
self.private.paused = false
|
||||||
self.private.currentTimeLeft = 0
|
self.private.currentTimeLeft = 0
|
||||||
|
self.private.triggerOnEnd = triggerOnEnd
|
||||||
|
|
||||||
Wait(0)
|
Wait(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
function timer:pause()
|
function timer:pause()
|
||||||
if self.private.paused then return end
|
if self.private.paused then return end
|
||||||
|
|
||||||
self.private.currentTimeLeft = self:getTimeLeft('ms') --[[@as number]]
|
self.private.currentTimeLeft = self:getTimeLeft('ms') --[[@as number]]
|
||||||
self.private.paused = true
|
self.private.paused = true
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,110 +0,0 @@
|
|||||||
local glm = require 'glm'
|
|
||||||
|
|
||||||
---@type table<number, CZone>
|
|
||||||
local Zones = {}
|
|
||||||
_ENV.Zones = Zones
|
|
||||||
|
|
||||||
local function removeZone(self)
|
|
||||||
Zones[self.id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local glm_polygon_contains = glm.polygon.contains
|
|
||||||
|
|
||||||
local function contains(self, coords)
|
|
||||||
return glm_polygon_contains(self.polygon, coords, self.thickness / 4)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function insideSphere(self, coords)
|
|
||||||
return #(self.coords - coords) < self.radius
|
|
||||||
end
|
|
||||||
|
|
||||||
local function convertToVector(coords)
|
|
||||||
local _type = type(coords)
|
|
||||||
|
|
||||||
if _type ~= 'vector3' then
|
|
||||||
if _type == 'table' or _type == 'vector4' then
|
|
||||||
return vec3(coords[1] or coords.x, coords[2] or coords.y, coords[3] or coords.z)
|
|
||||||
end
|
|
||||||
|
|
||||||
error(("expected type 'vector3' or 'table' (received %s)"):format(_type))
|
|
||||||
end
|
|
||||||
|
|
||||||
return coords
|
|
||||||
end
|
|
||||||
|
|
||||||
lib.zones = {
|
|
||||||
---@return CZone
|
|
||||||
poly = function(data)
|
|
||||||
data.id = #Zones + 1
|
|
||||||
data.thickness = data.thickness or 4
|
|
||||||
|
|
||||||
local pointN = #data.points
|
|
||||||
local points = table.create(pointN, 0)
|
|
||||||
|
|
||||||
for i = 1, pointN do
|
|
||||||
points[i] = convertToVector(data.points[i])
|
|
||||||
end
|
|
||||||
|
|
||||||
data.polygon = glm.polygon.new(points)
|
|
||||||
data.coords = data.polygon:centroid()
|
|
||||||
data.type = 'poly'
|
|
||||||
data.remove = removeZone
|
|
||||||
data.contains = contains
|
|
||||||
data.debug = nil
|
|
||||||
data.debugColour = nil
|
|
||||||
data.inside = nil
|
|
||||||
data.onEnter = nil
|
|
||||||
data.onExit = nil
|
|
||||||
|
|
||||||
Zones[data.id] = data
|
|
||||||
return data
|
|
||||||
end,
|
|
||||||
|
|
||||||
---@return CZone
|
|
||||||
box = function(data)
|
|
||||||
data.id = #Zones + 1
|
|
||||||
data.coords = convertToVector(data.coords)
|
|
||||||
data.size = data.size and convertToVector(data.size) / 2 or vec3(2)
|
|
||||||
data.thickness = data.size.z * 2 or 4
|
|
||||||
data.rotation = quat(data.rotation or 0, vec3(0, 0, 1))
|
|
||||||
data.polygon = (data.rotation * glm.polygon.new({
|
|
||||||
vec3(data.size.x, data.size.y, 0),
|
|
||||||
vec3(-data.size.x, data.size.y, 0),
|
|
||||||
vec3(-data.size.x, -data.size.y, 0),
|
|
||||||
vec3(data.size.x, -data.size.y, 0),
|
|
||||||
}) + data.coords)
|
|
||||||
data.type = 'box'
|
|
||||||
data.remove = removeZone
|
|
||||||
data.contains = contains
|
|
||||||
data.debug = nil
|
|
||||||
data.debugColour = nil
|
|
||||||
data.inside = nil
|
|
||||||
data.onEnter = nil
|
|
||||||
data.onExit = nil
|
|
||||||
|
|
||||||
Zones[data.id] = data
|
|
||||||
return data
|
|
||||||
end,
|
|
||||||
|
|
||||||
---@return CZone
|
|
||||||
sphere = function(data)
|
|
||||||
data.id = #Zones + 1
|
|
||||||
data.coords = convertToVector(data.coords)
|
|
||||||
data.radius = (data.radius or 2) + 0.0
|
|
||||||
data.type = 'sphere'
|
|
||||||
data.remove = removeZone
|
|
||||||
data.contains = insideSphere
|
|
||||||
data.debug = nil
|
|
||||||
data.debugColour = nil
|
|
||||||
data.inside = nil
|
|
||||||
data.onEnter = nil
|
|
||||||
data.onExit = nil
|
|
||||||
|
|
||||||
Zones[data.id] = data
|
|
||||||
return data
|
|
||||||
end,
|
|
||||||
|
|
||||||
getAllZones = function() return Zones end,
|
|
||||||
}
|
|
||||||
|
|
||||||
return lib.zones
|
|
||||||
@ -1,19 +1,20 @@
|
|||||||
local glm = require 'glm'
|
local glm = require 'glm'
|
||||||
|
|
||||||
---@class CZone
|
---@class ZoneProperties
|
||||||
---@field id number
|
---@field debug? boolean
|
||||||
---@field coords vector3
|
---@field debugColour? vector4
|
||||||
---@field distance number
|
|
||||||
---@field __type 'poly' | 'sphere' | 'box'
|
|
||||||
---@field debugColour vector4?
|
|
||||||
---@field setDebug fun(self: CZone, enable?: boolean, colour?: vector)
|
|
||||||
---@field remove fun()
|
|
||||||
---@field contains fun(self: CZone, coords?: vector3): boolean
|
|
||||||
---@field onEnter fun(self: CZone)?
|
---@field onEnter fun(self: CZone)?
|
||||||
---@field onExit fun(self: CZone)?
|
---@field onExit fun(self: CZone)?
|
||||||
---@field inside fun(self: CZone)?
|
---@field inside fun(self: CZone)?
|
||||||
---@field [string] any
|
---@field [string] any
|
||||||
|
|
||||||
|
---@class CZone : PolyZone, BoxZone, SphereZone
|
||||||
|
---@field id number
|
||||||
|
---@field __type 'poly' | 'sphere' | 'box'
|
||||||
|
---@field remove fun(self: self)
|
||||||
|
---@field setDebug fun(self: CZone, enable?: boolean, colour?: vector)
|
||||||
|
---@field contains fun(self: CZone, coords?: vector3, updateDistance?: boolean): boolean
|
||||||
|
|
||||||
---@type table<number, CZone>
|
---@type table<number, CZone>
|
||||||
local Zones = {}
|
local Zones = {}
|
||||||
_ENV.Zones = Zones
|
_ENV.Zones = Zones
|
||||||
@ -96,46 +97,66 @@ local function getTriangles(polygon)
|
|||||||
return triangles
|
return triangles
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type table<number, CZone>
|
local insideZones = lib.context == 'client' and {} --[[@as table<number, CZone>]]
|
||||||
local insideZones = {}
|
local exitingZones = lib.context == 'client' and lib.array:new() --[[@as Array<CZone>]]
|
||||||
---@type table<number, CZone>
|
local enteringZones = lib.context == 'client' and lib.array:new() --[[@as Array<CZone>]]
|
||||||
local enteringZones = {}
|
local nearbyZones = lib.array:new() --[[@as Array<CZone>]]
|
||||||
---@type table<number, CZone>
|
|
||||||
local exitingZones = {}
|
|
||||||
local enteringSize = 0
|
|
||||||
local exitingSize = 0
|
|
||||||
local tick
|
|
||||||
local glm_polygon_contains = glm.polygon.contains
|
local glm_polygon_contains = glm.polygon.contains
|
||||||
|
local tick
|
||||||
|
|
||||||
local function removeZone(self)
|
---@param zone CZone
|
||||||
Zones[self.id] = nil
|
local function removeZone(zone)
|
||||||
insideZones[self.id] = nil
|
Zones[zone.id] = nil
|
||||||
enteringZones[self.id] = nil
|
|
||||||
exitingZones[self.id] = nil
|
lib.grid.removeEntry(zone)
|
||||||
|
|
||||||
|
if lib.context == 'server' then return end
|
||||||
|
|
||||||
|
insideZones[zone.id] = nil
|
||||||
|
|
||||||
|
table.remove(exitingZones, exitingZones:indexOf(zone))
|
||||||
|
table.remove(enteringZones, enteringZones:indexOf(zone))
|
||||||
end
|
end
|
||||||
|
|
||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
|
if lib.context == 'server' then return end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local coords = GetEntityCoords(cache.ped)
|
local coords = GetEntityCoords(cache.ped)
|
||||||
|
local zones = lib.grid.getNearbyEntries(coords, function(entry) return entry.remove == removeZone end) --[[@as Array<CZone>]]
|
||||||
|
local cellX, cellY = lib.grid.getCellPosition(coords)
|
||||||
cache.coords = coords
|
cache.coords = coords
|
||||||
|
|
||||||
for _, zone in pairs(Zones) do
|
if cellX ~= cache.lastCellX or cellY ~= cache.lastCellY then
|
||||||
zone.distance = #(zone.coords - coords)
|
for i = 1, #nearbyZones do
|
||||||
local radius, contains = zone.radius, nil
|
local zone = nearbyZones[i]
|
||||||
|
|
||||||
if radius then
|
if zone.insideZone then
|
||||||
contains = zone.distance < radius
|
local contains = zone:contains(coords, true)
|
||||||
else
|
|
||||||
contains = glm_polygon_contains(zone.polygon, coords, zone.thickness / 4)
|
if not contains then
|
||||||
|
zone.insideZone = false
|
||||||
|
insideZones[zone.id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
cache.lastCellX = cellX
|
||||||
|
cache.lastCellY = cellY
|
||||||
|
end
|
||||||
|
|
||||||
|
nearbyZones = zones
|
||||||
|
|
||||||
|
for i = 1, #zones do
|
||||||
|
local zone = zones[i]
|
||||||
|
local contains = zone:contains(coords, true)
|
||||||
|
|
||||||
if contains then
|
if contains then
|
||||||
if not zone.insideZone then
|
if not zone.insideZone then
|
||||||
zone.insideZone = true
|
zone.insideZone = true
|
||||||
|
|
||||||
if zone.onEnter then
|
if zone.onEnter then
|
||||||
enteringSize += 1
|
enteringZones:push(zone)
|
||||||
enteringZones[enteringSize] = zone
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if zone.inside or zone.debug then
|
if zone.inside or zone.debug then
|
||||||
@ -148,8 +169,7 @@ CreateThread(function()
|
|||||||
insideZones[zone.id] = nil
|
insideZones[zone.id] = nil
|
||||||
|
|
||||||
if zone.onExit then
|
if zone.onExit then
|
||||||
exitingSize += 1
|
exitingZones:push(zone)
|
||||||
exitingZones[exitingSize] = zone
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -159,16 +179,18 @@ CreateThread(function()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local exitingSize = #exitingZones
|
||||||
|
local enteringSize = #enteringZones
|
||||||
|
|
||||||
if exitingSize > 0 then
|
if exitingSize > 0 then
|
||||||
table.sort(exitingZones, function(a, b)
|
table.sort(exitingZones, function(a, b)
|
||||||
return a.distance > b.distance
|
return a.distance < b.distance
|
||||||
end)
|
end)
|
||||||
|
|
||||||
for i = 1, exitingSize do
|
for i = exitingSize, 1, -1 do
|
||||||
exitingZones[i]:onExit()
|
exitingZones[i]:onExit()
|
||||||
end
|
end
|
||||||
|
|
||||||
exitingSize = 0
|
|
||||||
table.wipe(exitingZones)
|
table.wipe(exitingZones)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -181,7 +203,6 @@ CreateThread(function()
|
|||||||
enteringZones[i]:onEnter()
|
enteringZones[i]:onEnter()
|
||||||
end
|
end
|
||||||
|
|
||||||
enteringSize = 0
|
|
||||||
table.wipe(enteringZones)
|
table.wipe(enteringZones)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -242,12 +263,18 @@ local function debugSphere(self)
|
|||||||
self.debugColour.g, self.debugColour.b, self.debugColour.a, false, false, 0, false, false, false, false)
|
self.debugColour.g, self.debugColour.b, self.debugColour.a, false, false, 0, false, false, false, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function contains(self, coords)
|
local function contains(self, coords, updateDistance)
|
||||||
|
if updateDistance then self.distance = #(self.coords - coords) end
|
||||||
|
|
||||||
return glm_polygon_contains(self.polygon, coords, self.thickness / 4)
|
return glm_polygon_contains(self.polygon, coords, self.thickness / 4)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function insideSphere(self, coords)
|
local function insideSphere(self, coords, updateDistance)
|
||||||
return #(self.coords - coords) < self.radius
|
local distance = #(self.coords - coords)
|
||||||
|
|
||||||
|
if updateDistance then self.distance = distance end
|
||||||
|
|
||||||
|
return distance < self.radius
|
||||||
end
|
end
|
||||||
|
|
||||||
local function convertToVector(coords)
|
local function convertToVector(coords)
|
||||||
@ -291,137 +318,160 @@ local function setDebug(self, bool, colour)
|
|||||||
self.debug = self.__type == 'sphere' and debugSphere or debugPoly or nil
|
self.debug = self.__type == 'sphere' and debugSphere or debugPoly or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
lib.zones = {
|
---@param data ZoneProperties
|
||||||
---@return CZone
|
---@return CZone
|
||||||
poly = function(data)
|
local function setZone(data)
|
||||||
data.id = #Zones + 1
|
---@cast data CZone
|
||||||
data.thickness = data.thickness or 4
|
data.remove = removeZone
|
||||||
|
data.contains = data.contains or contains
|
||||||
|
|
||||||
local pointN = #data.points
|
if lib.context == 'client' then
|
||||||
local points = table.create(pointN, 0)
|
data.setDebug = setDebug
|
||||||
|
|
||||||
|
if data.debug then
|
||||||
|
data.debug = nil
|
||||||
|
|
||||||
|
data:setDebug(true, data.debugColour)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
data.debug = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
Zones[data.id] = data
|
||||||
|
lib.grid.addEntry(data)
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
lib.zones = {}
|
||||||
|
|
||||||
|
---@class PolyZone : ZoneProperties
|
||||||
|
---@field points vector3[]
|
||||||
|
---@field thickness? number
|
||||||
|
|
||||||
|
---@param data PolyZone
|
||||||
|
---@return CZone
|
||||||
|
function lib.zones.poly(data)
|
||||||
|
data.id = #Zones + 1
|
||||||
|
data.thickness = data.thickness or 4
|
||||||
|
|
||||||
|
local pointN = #data.points
|
||||||
|
local points = table.create(pointN, 0)
|
||||||
|
|
||||||
|
for i = 1, pointN do
|
||||||
|
points[i] = convertToVector(data.points[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
data.polygon = glm.polygon.new(points)
|
||||||
|
|
||||||
|
if not data.polygon:isPlanar() then
|
||||||
|
local zCoords = {}
|
||||||
|
|
||||||
for i = 1, pointN do
|
for i = 1, pointN do
|
||||||
points[i] = convertToVector(data.points[i])
|
local zCoord = points[i].z
|
||||||
|
|
||||||
|
if zCoords[zCoord] then
|
||||||
|
zCoords[zCoord] += 1
|
||||||
|
else
|
||||||
|
zCoords[zCoord] = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local coordsArray = {}
|
||||||
|
|
||||||
|
for coord, count in pairs(zCoords) do
|
||||||
|
coordsArray[#coordsArray + 1] = {
|
||||||
|
coord = coord,
|
||||||
|
count = count
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(coordsArray, function(a, b)
|
||||||
|
return a.count > b.count
|
||||||
|
end)
|
||||||
|
|
||||||
|
local zCoord = coordsArray[1].coord
|
||||||
|
local averageTo = 1
|
||||||
|
|
||||||
|
for i = 1, #coordsArray do
|
||||||
|
if coordsArray[i].count < coordsArray[1].count then
|
||||||
|
averageTo = i - 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if averageTo > 1 then
|
||||||
|
for i = 2, averageTo do
|
||||||
|
zCoord += coordsArray[i].coord
|
||||||
|
end
|
||||||
|
|
||||||
|
zCoord /= averageTo
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, pointN do
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
points[i] = vec3(data.points[i].xy, zCoord)
|
||||||
end
|
end
|
||||||
|
|
||||||
data.polygon = glm.polygon.new(points)
|
data.polygon = glm.polygon.new(points)
|
||||||
|
end
|
||||||
|
|
||||||
if not data.polygon:isPlanar() then
|
data.coords = data.polygon:centroid()
|
||||||
local zCoords = {}
|
data.__type = 'poly'
|
||||||
|
data.radius = lib.array.reduce(data.polygon, function(acc, point)
|
||||||
|
local distance = #(point - data.coords)
|
||||||
|
return distance > acc and distance or acc
|
||||||
|
end, 0)
|
||||||
|
|
||||||
for i = 1, pointN do
|
return setZone(data)
|
||||||
local zCoord = points[i].z
|
end
|
||||||
|
|
||||||
if zCoords[zCoord] then
|
---@class BoxZone : ZoneProperties
|
||||||
zCoords[zCoord] += 1
|
---@field coords vector3
|
||||||
else
|
---@field size? vector3
|
||||||
zCoords[zCoord] = 1
|
---@field rotation? number | vector3 | vector4 | matrix
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local coordsArray = {}
|
---@param data BoxZone
|
||||||
|
---@return CZone
|
||||||
|
function lib.zones.box(data)
|
||||||
|
data.id = #Zones + 1
|
||||||
|
data.coords = convertToVector(data.coords)
|
||||||
|
data.size = data.size and convertToVector(data.size) / 2 or vec3(2)
|
||||||
|
data.thickness = data.size.z * 2
|
||||||
|
data.rotation = quat(data.rotation or 0, vec3(0, 0, 1))
|
||||||
|
data.__type = 'box'
|
||||||
|
data.width = data.size.x * 2
|
||||||
|
data.length = data.size.y * 2
|
||||||
|
data.polygon = (data.rotation * glm.polygon.new({
|
||||||
|
vec3(data.size.x, data.size.y, 0),
|
||||||
|
vec3(-data.size.x, data.size.y, 0),
|
||||||
|
vec3(-data.size.x, -data.size.y, 0),
|
||||||
|
vec3(data.size.x, -data.size.y, 0),
|
||||||
|
}) + data.coords)
|
||||||
|
|
||||||
for coord, count in pairs(zCoords) do
|
return setZone(data)
|
||||||
coordsArray[#coordsArray + 1] = {
|
end
|
||||||
coord = coord,
|
|
||||||
count = count
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(coordsArray, function(a, b)
|
---@class SphereZone : ZoneProperties
|
||||||
return a.count > b.count
|
---@field coords vector3
|
||||||
end)
|
---@field radius? number
|
||||||
|
|
||||||
local zCoord = coordsArray[1].coord
|
---@param data SphereZone
|
||||||
local averageTo
|
---@return CZone
|
||||||
|
function lib.zones.sphere(data)
|
||||||
|
data.id = #Zones + 1
|
||||||
|
data.coords = convertToVector(data.coords)
|
||||||
|
data.radius = (data.radius or 2) + 0.0
|
||||||
|
data.__type = 'sphere'
|
||||||
|
data.contains = insideSphere
|
||||||
|
|
||||||
for i = 1, #coordsArray do
|
return setZone(data)
|
||||||
if coordsArray[i].count < coordsArray[1].count then
|
end
|
||||||
averageTo = i - 1
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if averageTo > 1 then
|
function lib.zones.getAllZones() return Zones end
|
||||||
for i = 2, averageTo do
|
|
||||||
zCoord += coordsArray[i].coord
|
|
||||||
end
|
|
||||||
|
|
||||||
zCoord /= averageTo
|
function lib.zones.getCurrentZones() return insideZones end
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, pointN do
|
function lib.zones.getNearbyZones() return nearbyZones end
|
||||||
points[i] = vec3(data.points[i].xy, zCoord)
|
|
||||||
end
|
|
||||||
|
|
||||||
data.polygon = glm.polygon.new(points)
|
|
||||||
end
|
|
||||||
|
|
||||||
data.coords = data.polygon:centroid()
|
|
||||||
data.__type = 'poly'
|
|
||||||
data.remove = removeZone
|
|
||||||
data.contains = contains
|
|
||||||
data.setDebug = setDebug
|
|
||||||
|
|
||||||
if data.debug then
|
|
||||||
data.debug = nil
|
|
||||||
|
|
||||||
data:setDebug(true, data.debugColour)
|
|
||||||
end
|
|
||||||
|
|
||||||
Zones[data.id] = data
|
|
||||||
return data
|
|
||||||
end,
|
|
||||||
|
|
||||||
---@return CZone
|
|
||||||
box = function(data)
|
|
||||||
data.id = #Zones + 1
|
|
||||||
data.coords = convertToVector(data.coords)
|
|
||||||
data.size = data.size and convertToVector(data.size) / 2 or vec3(2)
|
|
||||||
data.thickness = data.size.z * 2 or 4
|
|
||||||
data.rotation = quat(data.rotation or 0, vec3(0, 0, 1))
|
|
||||||
data.polygon = (data.rotation * glm.polygon.new({
|
|
||||||
vec3(data.size.x, data.size.y, 0),
|
|
||||||
vec3(-data.size.x, data.size.y, 0),
|
|
||||||
vec3(-data.size.x, -data.size.y, 0),
|
|
||||||
vec3(data.size.x, -data.size.y, 0),
|
|
||||||
}) + data.coords)
|
|
||||||
data.__type = 'box'
|
|
||||||
data.remove = removeZone
|
|
||||||
data.contains = contains
|
|
||||||
data.setDebug = setDebug
|
|
||||||
|
|
||||||
if data.debug then
|
|
||||||
data.debug = nil
|
|
||||||
|
|
||||||
data:setDebug(true, data.debugColour)
|
|
||||||
end
|
|
||||||
|
|
||||||
Zones[data.id] = data
|
|
||||||
return data
|
|
||||||
end,
|
|
||||||
|
|
||||||
---@return CZone
|
|
||||||
sphere = function(data)
|
|
||||||
data.id = #Zones + 1
|
|
||||||
data.coords = convertToVector(data.coords)
|
|
||||||
data.radius = (data.radius or 2) + 0.0
|
|
||||||
data.__type = 'sphere'
|
|
||||||
data.remove = removeZone
|
|
||||||
data.contains = insideSphere
|
|
||||||
data.setDebug = setDebug
|
|
||||||
|
|
||||||
if data.debug then
|
|
||||||
data:setDebug(true, data.debugColour)
|
|
||||||
end
|
|
||||||
|
|
||||||
Zones[data.id] = data
|
|
||||||
return data
|
|
||||||
end,
|
|
||||||
|
|
||||||
getAllZones = function() return Zones end,
|
|
||||||
|
|
||||||
getCurrentZones = function() return insideZones end,
|
|
||||||
}
|
|
||||||
|
|
||||||
return lib.zones
|
return lib.zones
|
||||||
@ -96,11 +96,6 @@ local lib = setmetatable({
|
|||||||
__call = call,
|
__call = call,
|
||||||
})
|
})
|
||||||
|
|
||||||
_ENV.lib = lib
|
|
||||||
|
|
||||||
-- Override standard Lua require with our own.
|
|
||||||
require = lib.require
|
|
||||||
|
|
||||||
local intervals = {}
|
local intervals = {}
|
||||||
--- Dream of a world where this PR gets accepted.
|
--- Dream of a world where this PR gets accepted.
|
||||||
---@param callback function | number
|
---@param callback function | number
|
||||||
@ -215,7 +210,9 @@ function lib.onCache(key, cb)
|
|||||||
table.insert(cacheEvents[key], cb)
|
table.insert(cacheEvents[key], cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
_ENV.lib = lib
|
||||||
_ENV.cache = cache
|
_ENV.cache = cache
|
||||||
|
_ENV.require = lib.require
|
||||||
|
|
||||||
local notifyEvent = ('__ox_notify_%s'):format(cache.resource)
|
local notifyEvent = ('__ox_notify_%s'):format(cache.resource)
|
||||||
|
|
||||||
|
|||||||
34
resources/[core]/ox_lib/locales/el.json
Normal file
34
resources/[core]/ox_lib/locales/el.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"language": "Ελληνικά",
|
||||||
|
"settings": "Ρυθμίσεις",
|
||||||
|
"ui": {
|
||||||
|
"cancel": "Ακύρωση",
|
||||||
|
"close": "Κλείσιμο",
|
||||||
|
"confirm": "Επιβεβαίωση",
|
||||||
|
"more": "Περισσότερα...",
|
||||||
|
"settings": {
|
||||||
|
"locale": "Αλλαγή γλώσσας",
|
||||||
|
"locale_description": "Τρέχουσα γλώσσα: ${language} (%s)",
|
||||||
|
"notification_audio": "Ήχος ειδοποίησης",
|
||||||
|
"notification_position": "Θέση ειδοποίησης"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"bottom": "Κάτω",
|
||||||
|
"bottom-left": "Κάτω αριστερά",
|
||||||
|
"bottom-right": "Κάτω δεξιά",
|
||||||
|
"center-left": "Κέντρο αριστερά",
|
||||||
|
"center-right": "Κέντρο δεξιά",
|
||||||
|
"top": "Πάνω",
|
||||||
|
"top-left": "Πάνω αριστερά",
|
||||||
|
"top-right": "Πάνω δεξιά"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"open_radial_menu": "Άνοιγμα κυκλικού μενού",
|
||||||
|
"cancel_progress": "Ακύρωση τρέχουσας γραμμής προόδου",
|
||||||
|
"txadmin_announcement": "Ανακοίνωση διακομιστή από τον %s",
|
||||||
|
"txadmin_dm": "Άμεσο μήνυμα από τον %s",
|
||||||
|
"txadmin_warn": "Έχετε προειδοποιηθεί από τον %s",
|
||||||
|
"txadmin_warn_content": "%s \nID Ενέργειας: %s",
|
||||||
|
"txadmin_scheduledrestart": "Προγραμματισμένη επανεκκίνηση"
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"language": "Lietuvių",
|
"language": "Lietuvių",
|
||||||
|
"settings": "Nustatymai",
|
||||||
"ui": {
|
"ui": {
|
||||||
"cancel": "Atšaukti",
|
"cancel": "Atšaukti",
|
||||||
"close": "Uždaryti",
|
"close": "Uždaryti",
|
||||||
@ -9,17 +10,17 @@
|
|||||||
"locale": "Pakeisti kalbą",
|
"locale": "Pakeisti kalbą",
|
||||||
"locale_description": "Dabartinė: ${language} (%s)",
|
"locale_description": "Dabartinė: ${language} (%s)",
|
||||||
"notification_audio": "Pranešimo garsas",
|
"notification_audio": "Pranešimo garsas",
|
||||||
"notification_position": "Notification position"
|
"notification_position": "Pranešimo pozicija"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"bottom": "Bottom",
|
"bottom": "Apačioje",
|
||||||
"bottom-left": "Bottom-left",
|
"bottom-left": "Apačioje-kairėje",
|
||||||
"bottom-right": "Bottom-right",
|
"bottom-right": "Apačioje-dešinėje",
|
||||||
"center-left": "Center-left",
|
"center-left": "Centre-kairėje",
|
||||||
"center-right": "Center-right",
|
"center-right": "Centre-dešinėje",
|
||||||
"top": "Top",
|
"top": "Viršuje",
|
||||||
"top-left": "Top-left",
|
"top-left": "Viršuje-kairėje",
|
||||||
"top-right": "Top-right"
|
"top-right": "Viršuje-dešinėje"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"open_radial_menu": "Atidaryti radialinį meniu",
|
"open_radial_menu": "Atidaryti radialinį meniu",
|
||||||
|
|||||||
@ -1,29 +1,30 @@
|
|||||||
{
|
{
|
||||||
"language": "Nederlands",
|
"language": "Nederlands",
|
||||||
|
"settings": "Instellingen",
|
||||||
"ui": {
|
"ui": {
|
||||||
"cancel": "Annuleren",
|
"cancel": "Annuleren",
|
||||||
"close": "Sluiten",
|
"close": "Sluiten",
|
||||||
"confirm": "Bevestigen",
|
"confirm": "Bevestigen",
|
||||||
"more": "Meer...",
|
"more": "Meer...",
|
||||||
"settings": {
|
"settings": {
|
||||||
"locale": "Change locale",
|
"locale": "Taal wijzigen",
|
||||||
"locale_description": "Current language: ${language} (%s)",
|
"locale_description": "Huidige taal: ${language} (%s)",
|
||||||
"notification_audio": "Notification audio",
|
"notification_audio": "Meldingsgeluid",
|
||||||
"notification_position": "Notification position"
|
"notification_position": "Meldingspositie"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"bottom": "Bottom",
|
"bottom": "Onder",
|
||||||
"bottom-left": "Bottom-left",
|
"bottom-left": "Linksonder",
|
||||||
"bottom-right": "Bottom-right",
|
"bottom-right": "Rechtsonder",
|
||||||
"center-left": "Center-left",
|
"center-left": "Linksmidden",
|
||||||
"center-right": "Center-right",
|
"center-right": "Rechtsmidden",
|
||||||
"top": "Top",
|
"top": "Boven",
|
||||||
"top-left": "Top-left",
|
"top-left": "Linksboven",
|
||||||
"top-right": "Top-right"
|
"top-right": "Rechtsboven"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"open_radial_menu": "Open radial menu",
|
"open_radial_menu": "Radiaal menu openen",
|
||||||
"cancel_progress": "Cancel current progress bar",
|
"cancel_progress": "Huidige voortgangsbalk annuleren",
|
||||||
"txadmin_announcement": "Server mededeling door %s",
|
"txadmin_announcement": "Server mededeling door %s",
|
||||||
"txadmin_dm": "Bericht van %s",
|
"txadmin_dm": "Bericht van %s",
|
||||||
"txadmin_warn": "Je hebt een waarschuwing gekregen van %s",
|
"txadmin_warn": "Je hebt een waarschuwing gekregen van %s",
|
||||||
|
|||||||
@ -6,27 +6,27 @@
|
|||||||
"confirm": "Confirmar",
|
"confirm": "Confirmar",
|
||||||
"more": "Mais...",
|
"more": "Mais...",
|
||||||
"settings": {
|
"settings": {
|
||||||
"locale": "Change locale",
|
"locale": "Alterar idioma",
|
||||||
"locale_description": "Current language: ${language} (%s)",
|
"locale_description": "Idioma atual: ${language} (%s)",
|
||||||
"notification_audio": "Notification audio",
|
"notification_audio": "Áudio de notificação",
|
||||||
"notification_position": "Notification position"
|
"notification_position": "Posição da notificação"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"bottom": "Bottom",
|
"bottom": "Inferior",
|
||||||
"bottom-left": "Bottom-left",
|
"bottom-left": "Inferior esquerdo",
|
||||||
"bottom-right": "Bottom-right",
|
"bottom-right": "Inferior direito",
|
||||||
"center-left": "Center-left",
|
"center-left": "Centro-esquerdo",
|
||||||
"center-right": "Center-right",
|
"center-right": "Centro-direito",
|
||||||
"top": "Top",
|
"top": "Superior",
|
||||||
"top-left": "Top-left",
|
"top-left": "Superior esquerdo",
|
||||||
"top-right": "Top-right"
|
"top-right": "Superior direito"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"open_radial_menu": "Open radial menu",
|
"open_radial_menu": "Abrir menu radial",
|
||||||
"cancel_progress": "Cancel current progress bar",
|
"cancel_progress": "Cancelar barra de progresso atual",
|
||||||
"txadmin_announcement": "Server announcement by %s",
|
"txadmin_announcement": "Anúncio por %s",
|
||||||
"txadmin_dm": "Direct Message from %s",
|
"txadmin_dm": "Mensagem de %s",
|
||||||
"txadmin_warn": "You have been warned by %s",
|
"txadmin_warn": "Você foi alertado por %s",
|
||||||
"txadmin_warn_content": "%s \nAction ID: %s",
|
"txadmin_warn_content": "%s \nID do aviso: %s",
|
||||||
"txadmin_scheduledrestart": "Scheduled Restart"
|
"txadmin_scheduledrestart": "Reinício agendado"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"language": "Türkçe",
|
"language": "Türkçe",
|
||||||
|
"settings": "Ayarlar",
|
||||||
"ui": {
|
"ui": {
|
||||||
"cancel": "İptal",
|
"cancel": "İptal",
|
||||||
"close": "Kapat",
|
"close": "Kapat",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"language": "简体中文",
|
"language": "简体中文",
|
||||||
|
"settings": "设置",
|
||||||
"ui": {
|
"ui": {
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"close": "关闭",
|
"close": "关闭",
|
||||||
|
|||||||
@ -1,29 +1,30 @@
|
|||||||
{
|
{
|
||||||
"language": "繁體中文",
|
"language": "繁體中文",
|
||||||
|
"settings": "設置",
|
||||||
"ui": {
|
"ui": {
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"close": "關閉",
|
"close": "關閉",
|
||||||
"confirm": "確認",
|
"confirm": "確認",
|
||||||
"more": "更多...",
|
"more": "更多...",
|
||||||
"settings": {
|
"settings": {
|
||||||
"locale": "Change locale",
|
"locale": "更改語言",
|
||||||
"locale_description": "Current language: ${language} (%s)",
|
"locale_description": "當前語言: ${language} (%s)",
|
||||||
"notification_audio": "Notification audio",
|
"notification_audio": "通知提示音",
|
||||||
"notification_position": "Notification position"
|
"notification_position": "通知位置"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"bottom": "Bottom",
|
"bottom": "底部",
|
||||||
"bottom-left": "Bottom-left",
|
"bottom-left": "左下",
|
||||||
"bottom-right": "Bottom-right",
|
"bottom-right": "右下",
|
||||||
"center-left": "Center-left",
|
"center-left": "左側居中",
|
||||||
"center-right": "Center-right",
|
"center-right": "右側居中",
|
||||||
"top": "Top",
|
"top": "頂部",
|
||||||
"top-left": "Top-left",
|
"top-left": "左上",
|
||||||
"top-right": "Top-right"
|
"top-right": "右上"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"open_radial_menu": "Open radial menu",
|
"open_radial_menu": "打開輪盤菜單",
|
||||||
"cancel_progress": "Cancel current progress bar",
|
"cancel_progress": "取消當前進度條",
|
||||||
"txadmin_announcement": "來自 %s 的伺服器通告",
|
"txadmin_announcement": "來自 %s 的伺服器通告",
|
||||||
"txadmin_dm": "來自 %s 的訊息",
|
"txadmin_dm": "來自 %s 的訊息",
|
||||||
"txadmin_warn": "您被 %s 警告了",
|
"txadmin_warn": "您被 %s 警告了",
|
||||||
|
|||||||
@ -26,6 +26,10 @@ CreateThread(function()
|
|||||||
local vehicle = GetVehiclePedIsIn(ped, false)
|
local vehicle = GetVehiclePedIsIn(ped, false)
|
||||||
|
|
||||||
if vehicle > 0 then
|
if vehicle > 0 then
|
||||||
|
if vehicle ~= cache.vehicle then
|
||||||
|
cache:set('seat', false)
|
||||||
|
end
|
||||||
|
|
||||||
cache:set('vehicle', vehicle)
|
cache:set('vehicle', vehicle)
|
||||||
|
|
||||||
if not cache.seat or GetPedInVehicleSeat(vehicle, cache.seat) ~= ped then
|
if not cache.seat or GetPedInVehicleSeat(vehicle, cache.seat) ~= ped then
|
||||||
|
|||||||
57
resources/[core]/ox_lib/resource/callbacks/shared.lua
Normal file
57
resources/[core]/ox_lib/resource/callbacks/shared.lua
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
local registeredCallbacks = {}
|
||||||
|
|
||||||
|
AddEventHandler('onResourceStop', function(resourceName)
|
||||||
|
if cache.resource == 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 lib.setValidCallback(callbackName, isValid)
|
||||||
|
local resourceName = GetInvokingResource() or cache.resource
|
||||||
|
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
|
||||||
|
|
||||||
|
lib.print.verbose(("set valid callback '%s' for resource '%s'"):format(callbackName, resourceName))
|
||||||
|
|
||||||
|
registeredCallbacks[callbackName] = resourceName
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.isCallbackValid(callbackName)
|
||||||
|
return registeredCallbacks[callbackName] == GetInvokingResource() or cache.resource
|
||||||
|
end
|
||||||
|
|
||||||
|
local cbEvent = '__ox_cb_%s'
|
||||||
|
|
||||||
|
RegisterNetEvent('ox_lib:validateCallback', function(callbackName, invokingResource, key)
|
||||||
|
if registeredCallbacks[callbackName] then return end
|
||||||
|
|
||||||
|
local event = cbEvent:format(invokingResource)
|
||||||
|
|
||||||
|
if cache.game == 'fxserver' then
|
||||||
|
return TriggerClientEvent(event, source, key, 'cb_invalid')
|
||||||
|
end
|
||||||
|
|
||||||
|
TriggerServerEvent(event, key, 'cb_invalid')
|
||||||
|
end)
|
||||||
@ -49,6 +49,11 @@ function lib.showMenu(id, startIndex)
|
|||||||
if not menu then
|
if not menu then
|
||||||
error(('No menu with id %s was found'):format(id))
|
error(('No menu with id %s was found'):format(id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if table.type(menu.options) == 'empty' then
|
||||||
|
error(('Can\'t open empty menu with id %s'):format(id))
|
||||||
|
end
|
||||||
|
|
||||||
if not openMenu then
|
if not openMenu then
|
||||||
local control = cache.game == 'fivem' and 140 or 0xE30CD707
|
local control = cache.game == 'fivem' and 140 or 0xE30CD707
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,8 @@ local function createProp(ped, prop)
|
|||||||
local coords = GetEntityCoords(ped)
|
local coords = GetEntityCoords(ped)
|
||||||
local object = CreateObject(prop.model, coords.x, coords.y, coords.z, false, false, false)
|
local object = CreateObject(prop.model, coords.x, coords.y, coords.z, false, false, false)
|
||||||
|
|
||||||
AttachEntityToEntity(object, ped, GetPedBoneIndex(ped, prop.bone or 60309), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, prop.rotOrder or 0, true)
|
AttachEntityToEntity(object, ped, GetPedBoneIndex(ped, prop.bone or 60309), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true,
|
||||||
|
true, false, true, prop.rotOrder or 0, true)
|
||||||
SetModelAsNoLongerNeeded(prop.model)
|
SetModelAsNoLongerNeeded(prop.model)
|
||||||
|
|
||||||
return object
|
return object
|
||||||
@ -62,6 +63,7 @@ local controls = {
|
|||||||
INPUT_VEH_MOUSE_CONTROL_OVERRIDE = isFivem and 106 or 0x39CCABD5
|
INPUT_VEH_MOUSE_CONTROL_OVERRIDE = isFivem and 106 or 0x39CCABD5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@param data ProgressProps
|
||||||
local function startProgress(data)
|
local function startProgress(data)
|
||||||
playerState.invBusy = true
|
playerState.invBusy = true
|
||||||
progress = data
|
progress = data
|
||||||
@ -71,10 +73,11 @@ local function startProgress(data)
|
|||||||
if anim.dict then
|
if anim.dict then
|
||||||
lib.requestAnimDict(anim.dict)
|
lib.requestAnimDict(anim.dict)
|
||||||
|
|
||||||
TaskPlayAnim(cache.ped, anim.dict, anim.clip, anim.blendIn or 3.0, anim.blendOut or 1.0, anim.duration or -1, anim.flag or 49, anim.playbackRate or 0, anim.lockX, anim.lockY, anim.lockZ)
|
TaskPlayAnim(cache.ped, anim.dict, anim.clip, anim.blendIn or 3.0, anim.blendOut or 1.0, anim.duration or -1, anim.flag or 49, anim.playbackRate or 0,
|
||||||
|
anim.lockX, anim.lockY, anim.lockZ)
|
||||||
RemoveAnimDict(anim.dict)
|
RemoveAnimDict(anim.dict)
|
||||||
elseif anim.scenario then
|
elseif anim.scenario then
|
||||||
TaskStartScenarioInPlace(cache.ped, anim.scenario, 0, anim.playEnter ~= nil and anim.playEnter or true)
|
TaskStartScenarioInPlace(cache.ped, anim.scenario, 0, anim.playEnter == nil or anim.playEnter --[[@as boolean]])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -83,6 +86,7 @@ local function startProgress(data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local disable = data.disable
|
local disable = data.disable
|
||||||
|
local startTime = GetGameTimer()
|
||||||
|
|
||||||
while progress do
|
while progress do
|
||||||
if disable then
|
if disable then
|
||||||
@ -138,8 +142,9 @@ local function startProgress(data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
playerState.invBusy = false
|
playerState.invBusy = false
|
||||||
|
local duration = progress ~= false and GetGameTimer() - startTime + 100 -- give slight leeway
|
||||||
|
|
||||||
if progress == false then
|
if progress == false or duration <= data.duration then
|
||||||
SendNUIMessage({ action = 'progressCancel' })
|
SendNUIMessage({ action = 'progressCancel' })
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@ -243,13 +248,13 @@ AddStateBagChangeHandler('lib:progressProps', nil, function(bagName, key, value,
|
|||||||
local playerProps = createdProps[serverId]
|
local playerProps = createdProps[serverId]
|
||||||
|
|
||||||
if value.model then
|
if value.model then
|
||||||
playerProps[#playerProps+1] = createProp(ped, value)
|
playerProps[#playerProps + 1] = createProp(ped, value)
|
||||||
else
|
else
|
||||||
for i = 1, #value do
|
for i = 1, #value do
|
||||||
local prop = value[i]
|
local prop = value[i]
|
||||||
|
|
||||||
if prop then
|
if prop then
|
||||||
playerProps[#playerProps+1] = createProp(ped, prop)
|
playerProps[#playerProps + 1] = createProp(ped, prop)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -80,6 +80,7 @@ if cache.game == 'redm' then return end
|
|||||||
---@field modLivery? number
|
---@field modLivery? number
|
||||||
---@field modRoofLivery? number
|
---@field modRoofLivery? number
|
||||||
---@field modLightbar? number
|
---@field modLightbar? number
|
||||||
|
---@field livery? number
|
||||||
---@field windows? number[]
|
---@field windows? number[]
|
||||||
---@field doors? number[]
|
---@field doors? number[]
|
||||||
---@field tyres? table<number | string, 1 | 2>
|
---@field tyres? table<number | string, 1 | 2>
|
||||||
@ -151,13 +152,6 @@ function lib.getVehicleProperties(vehicle)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local modLiveryCount = GetVehicleLiveryCount(vehicle)
|
|
||||||
local modLivery = GetVehicleLivery(vehicle)
|
|
||||||
|
|
||||||
if modLiveryCount == -1 or modLivery == -1 then
|
|
||||||
modLivery = GetVehicleMod(vehicle, 48)
|
|
||||||
end
|
|
||||||
|
|
||||||
local damage = {
|
local damage = {
|
||||||
windows = {},
|
windows = {},
|
||||||
doors = {},
|
doors = {},
|
||||||
@ -273,9 +267,10 @@ function lib.getVehicleProperties(vehicle)
|
|||||||
modTank = GetVehicleMod(vehicle, 45),
|
modTank = GetVehicleMod(vehicle, 45),
|
||||||
modWindows = GetVehicleMod(vehicle, 46),
|
modWindows = GetVehicleMod(vehicle, 46),
|
||||||
modDoorR = GetVehicleMod(vehicle, 47),
|
modDoorR = GetVehicleMod(vehicle, 47),
|
||||||
modLivery = modLivery,
|
modLivery = GetVehicleMod(vehicle, 48),
|
||||||
modRoofLivery = GetVehicleRoofLivery(vehicle),
|
modRoofLivery = GetVehicleRoofLivery(vehicle),
|
||||||
modLightbar = GetVehicleMod(vehicle, 49),
|
modLightbar = GetVehicleMod(vehicle, 49),
|
||||||
|
livery = GetVehicleLivery(vehicle),
|
||||||
windows = damage.windows,
|
windows = damage.windows,
|
||||||
doors = damage.doors,
|
doors = damage.doors,
|
||||||
tyres = damage.tyres,
|
tyres = damage.tyres,
|
||||||
@ -348,7 +343,7 @@ function lib.setVehicleProperties(vehicle, props, fixVehicle)
|
|||||||
ClearVehicleCustomPrimaryColour(vehicle)
|
ClearVehicleCustomPrimaryColour(vehicle)
|
||||||
SetVehicleColours(vehicle, props.color1 --[[@as number]], colorSecondary --[[@as number]])
|
SetVehicleColours(vehicle, props.color1 --[[@as number]], colorSecondary --[[@as number]])
|
||||||
else
|
else
|
||||||
if props.paintType1 then SetVehicleModColor_1(vehicle, props.paintType1, colorPrimary, pearlescentColor) end
|
if props.paintType1 then SetVehicleModColor_1(vehicle, props.paintType1, 0, props.pearlescentColor or 0) end
|
||||||
|
|
||||||
SetVehicleCustomPrimaryColour(vehicle, props.color1[1], props.color1[2], props.color1[3])
|
SetVehicleCustomPrimaryColour(vehicle, props.color1[1], props.color1[2], props.color1[3])
|
||||||
end
|
end
|
||||||
@ -359,7 +354,7 @@ function lib.setVehicleProperties(vehicle, props, fixVehicle)
|
|||||||
ClearVehicleCustomSecondaryColour(vehicle)
|
ClearVehicleCustomSecondaryColour(vehicle)
|
||||||
SetVehicleColours(vehicle, props.color1 or colorPrimary --[[@as number]], props.color2 --[[@as number]])
|
SetVehicleColours(vehicle, props.color1 or colorPrimary --[[@as number]], props.color2 --[[@as number]])
|
||||||
else
|
else
|
||||||
if props.paintType2 then SetVehicleModColor_2(vehicle, props.paintType2, colorSecondary) end
|
if props.paintType2 then SetVehicleModColor_2(vehicle, props.paintType2, 0) end
|
||||||
|
|
||||||
SetVehicleCustomSecondaryColour(vehicle, props.color2[1], props.color2[2], props.color2[3])
|
SetVehicleCustomSecondaryColour(vehicle, props.color2[1], props.color2[2], props.color2[3])
|
||||||
end
|
end
|
||||||
@ -623,7 +618,6 @@ function lib.setVehicleProperties(vehicle, props, fixVehicle)
|
|||||||
|
|
||||||
if props.modLivery then
|
if props.modLivery then
|
||||||
SetVehicleMod(vehicle, 48, props.modLivery, false)
|
SetVehicleMod(vehicle, 48, props.modLivery, false)
|
||||||
SetVehicleLivery(vehicle, props.modLivery)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if props.modRoofLivery then
|
if props.modRoofLivery then
|
||||||
@ -634,6 +628,10 @@ function lib.setVehicleProperties(vehicle, props, fixVehicle)
|
|||||||
SetVehicleMod(vehicle, 49, props.modLightbar, false)
|
SetVehicleMod(vehicle, 49, props.modLightbar, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if props.livery then
|
||||||
|
SetVehicleLivery(vehicle, props.livery)
|
||||||
|
end
|
||||||
|
|
||||||
if props.bulletProofTyres ~= nil then
|
if props.bulletProofTyres ~= nil then
|
||||||
SetVehicleTyresCanBurst(vehicle, props.bulletProofTyres)
|
SetVehicleTyresCanBurst(vehicle, props.bulletProofTyres)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -37,6 +37,10 @@ exports.ox_target:addPolyZone({
|
|||||||
})
|
})
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local function formatNumber(num)
|
||||||
|
return tostring(num):gsub(",", ".")
|
||||||
|
end
|
||||||
|
|
||||||
local parse = {
|
local parse = {
|
||||||
poly = function(data)
|
poly = function(data)
|
||||||
local points = {}
|
local points = {}
|
||||||
@ -82,18 +86,34 @@ local parse = {
|
|||||||
pattern = {
|
pattern = {
|
||||||
'local box = lib.zones.box({\n',
|
'local box = lib.zones.box({\n',
|
||||||
('\tname = "%s",\n'):format(data.name),
|
('\tname = "%s",\n'):format(data.name),
|
||||||
('\tcoords = vec3(%s, %s, %s),\n'):format(data.xCoord, data.yCoord, data.zCoord),
|
('\tcoords = vec3(%s, %s, %s),\n'):format(
|
||||||
('\tsize = vec3(%s, %s, %s),\n'):format(data.width, data.length, data.height),
|
formatNumber(data.xCoord),
|
||||||
('\trotation = %s,\n'):format(data.heading),
|
formatNumber(data.yCoord),
|
||||||
|
formatNumber(data.zCoord)
|
||||||
|
),
|
||||||
|
('\tsize = vec3(%s, %s, %s),\n'):format(
|
||||||
|
formatNumber(data.width),
|
||||||
|
formatNumber(data.length),
|
||||||
|
formatNumber(data.height)
|
||||||
|
),
|
||||||
|
('\trotation = %s,\n'):format(formatNumber(data.heading)),
|
||||||
'})\n',
|
'})\n',
|
||||||
}
|
}
|
||||||
elseif data.format == 'array' then
|
elseif data.format == 'array' then
|
||||||
pattern = {
|
pattern = {
|
||||||
'{\n',
|
'{\n',
|
||||||
('\tname = "%s",\n'):format(data.name),
|
('\tname = "%s",\n'):format(data.name),
|
||||||
('\tcoords = vec3(%s, %s, %s),\n'):format(data.xCoord, data.yCoord, data.zCoord),
|
('\tcoords = vec3(%s, %s, %s),\n'):format(
|
||||||
('\tsize = vec3(%s, %s, %s),\n'):format(data.width, data.length, data.height),
|
formatNumber(data.xCoord),
|
||||||
('\trotation = %s,\n'):format(data.heading),
|
formatNumber(data.yCoord),
|
||||||
|
formatNumber(data.zCoord)
|
||||||
|
),
|
||||||
|
('\tsize = vec3(%s, %s, %s),\n'):format(
|
||||||
|
formatNumber(data.width),
|
||||||
|
formatNumber(data.length),
|
||||||
|
formatNumber(data.height)
|
||||||
|
),
|
||||||
|
('\trotation = %s,\n'):format(formatNumber(data.heading)),
|
||||||
'},\n',
|
'},\n',
|
||||||
}
|
}
|
||||||
elseif data.format == 'target' then
|
elseif data.format == 'target' then
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
@import"https://fonts.googleapis.com/css2?family=Poppins:wght@200;400;500;700&display=swap";@import"https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500&display=swap";@import"https://fonts.googleapis.com/css2?family=Fira+Mono&display=swap";@import"https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap";@import"https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap";@import"https://use.typekit.net/wxh5ury.css";@import"https://use.typekit.net/qgr5ebd.css";html{color-scheme:normal!important}body{background:none!important;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;height:100vh;user-select:none;overflow:hidden!important}p{margin:0}#root{height:100%}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}@keyframes progress-bar{0%{width:0%}to{width:100%}}::-webkit-scrollbar{background-color:transparent;padding:0;margin:0;width:0;height:0}.toast-inform{background-color:#2980b9}.toast-success{background-color:#27ae60}.toast-error{background-color:#c0392b}
|
|
||||||
File diff suppressed because one or more lines are too long
1041
resources/[core]/ox_lib/web/build/assets/index-DA6-Nmx5.js
Normal file
1041
resources/[core]/ox_lib/web/build/assets/index-DA6-Nmx5.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Black.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Black.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-BlackItalic.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-BlackItalic.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Bold.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-BoldItalic.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-ExtraBold.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-ExtraBold.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-ExtraLight.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-ExtraLight.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Italic.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Italic.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Light.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Light.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-LightItalic.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-LightItalic.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Medium.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Medium.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-MediumItalic.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Regular.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-SemiBold.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-SemiBold.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Thin.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-Thin.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-ThinItalic.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/Roboto-ThinItalic.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Bold.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Bold.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Italic.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Italic.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Light.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Light.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Medium.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Medium.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Regular.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Regular.ttf
Normal file
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-SemiBold.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-SemiBold.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Thin.ttf
Normal file
BIN
resources/[core]/ox_lib/web/build/fonts/RobotoMono-Thin.ttf
Normal file
Binary file not shown.
Binary file not shown.
@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>NUI React Boilerplate</title>
|
<title>NUI React Boilerplate</title>
|
||||||
<script type="module" crossorigin src="./assets/index-K9iBfj89.js"></script>
|
<script type="module" crossorigin src="./assets/index-DA6-Nmx5.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-3x7-Y31P.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-BgkLwDpx.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user