15 Commits

Author SHA1 Message Date
github-actions
ae16b78bc4 chore: bump version to v3.32.2 2025-12-21 17:18:40 +00:00
Christopher
3ea020cc50 feat(imports/logger): support metadata table for Fivemanage logger (#75) 2025-12-21 16:41:21 +01:00
Austin Dunn
db0bd16f0f Feat: Selector (#59)
* selector init

* fixes

* recalculate set weights on add/update
2025-12-08 00:19:59 +01:00
github-actions
01935adf8e chore: bump version to v3.32.1 2025-11-03 20:15:20 +00:00
Maximus7474
220c0c91ec fix(progress): client statebag change handler double creating the props (#60) 2025-11-03 21:13:15 +01:00
github-actions
855e1e4a36 chore: bump version to v3.32.0 2025-11-03 15:39:46 +00:00
PotatoFarmer441
a2f6b9a9e9 fix(progress): prop replication overload (#58) 2025-11-03 16:35:39 +01:00
RuksH4n
13a24baee4 fix(table_matches): remove leftover strict shape check from df7a6b9 (#50)
- The previous fix (commit df7a6b9) mistakenly left in a table.type
  / #t1 == #t2 check that breaks equality for {} and {x=nil}.
- This removes that leftover check, restoring the intended deep
  equality semantics.

Signed-off-by: RuksH4n <77233680+rukshanchamindu@users.noreply.github.com>
2025-09-13 12:47:16 +02:00
RuksH4n
df7a6b9c21 fix(table): restore expected behavior in table_matches (#49)
- The refactor in [Commit 4c460e0](4c460e0bd9) introduced stricter type checks (using table.type and array length comparison), which caused empty tables and tables with nil values to be considered unequal. 
- This commit removes the strict shape checks and restores the original deep equality semantics, ensuring {} == {x=nil}.

Signed-off-by: RuksH4n <77233680+rukshanchamindu@users.noreply.github.com>
2025-09-10 14:17:44 +02:00
github-actions
faa9dda57f chore: bump version to v3.31.4 2025-09-09 16:48:13 +00:00
Swellington Soares
619ecc9e55 feat(imports/dui): added mising dui methods (#47) 2025-09-09 18:47:01 +02:00
Maximus7474
56c0a91022 feat(imports/logger): add definable dataset for fivemanage logging (#45) 2025-09-04 18:48:50 +02:00
PotatoFarmer441
d6417b9a84 fix(zones): set zone distance on creation (#44)
* fix(zones): set zone distance on creation

Signed-off-by: PotatoFarmer441 <helioau@live.com.au>

* Update shared.lua

Signed-off-by: PotatoFarmer441 <helioau@live.com.au>

---------

Signed-off-by: PotatoFarmer441 <helioau@live.com.au>
2025-08-30 13:39:20 +02:00
github-actions
ac9625d33a chore: bump version to v3.31.3 2025-08-18 12:00:25 +00:00
Zoo
2ab9114d2f chore(package/index): export addKeybind function (#40) 2025-08-18 13:58:42 +02:00
10 changed files with 284 additions and 28 deletions

View File

@@ -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.31.2' version '3.32.2'
license 'LGPL-3.0-or-later' license 'LGPL-3.0-or-later'
repository 'https://github.com/communityox/ox_lib' repository 'https://github.com/communityox/ox_lib'
description 'A library of shared functions to utilise in other resources.' description 'A library of shared functions to utilise in other resources.'

View File

@@ -84,6 +84,29 @@ function lib.dui:sendMessage(message)
end end
end end
---@param x number
---@param y number
function lib.dui:sendMouseMove(x, y)
SendDuiMouseMove(self.duiObject, x, y)
end
---@param button 'left' | 'middle' | 'right'
function lib.dui:sendMouseDown(button)
SendDuiMouseDown(self.duiObject, button)
end
---@param button 'left' | 'middle' | 'right'
function lib.dui:sendMouseUp(button)
SendDuiMouseUp(self.duiObject, button)
end
---@param deltaX number
---@param deltaY number
function lib.dui:sendMouseWheel(deltaX, deltaY)
SendDuiMouseWheel(self.duiObject, deltaY, deltaX)
end
AddEventHandler('onResourceStop', function(resourceName) AddEventHandler('onResourceStop', function(resourceName)
if cache.resource ~= resourceName then return end if cache.resource ~= resourceName then return end

View File

@@ -94,6 +94,7 @@ end
if service == 'fivemanage' then if service == 'fivemanage' then
local key = GetConvar('fivemanage:key', '') local key = GetConvar('fivemanage:key', '')
local dataset = GetConvar('fivemanage:dataset', '')
if key ~= '' then if key ~= '' then
local endpoint = 'https://api.fivemanage.com/api/logs/batch' local endpoint = 'https://api.fivemanage.com/api/logs/batch'
@@ -101,9 +102,13 @@ if service == 'fivemanage' then
local headers = { local headers = {
['Content-Type'] = 'application/json', ['Content-Type'] = 'application/json',
['Authorization'] = key, ['Authorization'] = key,
['User-Agent'] = 'ox_lib' ['User-Agent'] = 'ox_lib',
} }
if dataset ~= "" then
headers['X-Fivemanage-Dataset'] = dataset
end
function lib.logger(source, event, message, ...) function lib.logger(source, event, message, ...)
if not buffer then if not buffer then
buffer = {} buffer = {}
@@ -123,17 +128,43 @@ if service == 'fivemanage' then
end) end)
end end
local metadata = {
hostname = hostname,
service = event,
source = source,
}
local playerTags = formatTags(source, nil)
if playerTags and type(playerTags) == 'string' then
local tempTable = { string.strsplit(',', playerTags) }
for _, v in pairs(tempTable) do
local key, value = string.strsplit(':', v)
if key and value then
metadata[key] = value
end
end
end
local args = { ... }
for _, arg in pairs(args) do
if type(arg) == 'table' then
for k, v in pairs(arg) do
metadata[k] = v
end
elseif type(arg) == 'string' then
local key, value = string.strsplit(':', arg)
if key and value then
metadata[key] = value
end
end
end
bufferSize += 1 bufferSize += 1
buffer[bufferSize] = { buffer[bufferSize] = {
level = "info", level = "info",
message = message, message = message,
resource = cache.resource, resource = cache.resource,
metadata = { metadata = metadata,
hostname = hostname,
service = event,
source = source,
tags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil),
}
} }
end end
end end

175
imports/selector/shared.lua Normal file
View File

@@ -0,0 +1,175 @@
---@alias OxSelectorItem {[1]: number, [2]: any}
---@alias OxSelectorSet OxSelectorItem[]
---@class OxSelector: OxClass
---@field private sets OxSelectorSet | table<string, OxSelectorItem[]>
---@field private totalWeights table<string, number>
local OxSelector = lib.class("OxSelector")
local DEFAULT_SET = 'default'
local deepClone = lib.table.deepclone
local function calculateTotalWeight(set)
local total = 0
for i = 1, #set do
local item = set[i]
assert(type(item) == "table", "Each OxSelectorItem must be a table")
local weight = item[1]
assert(type(weight) == "number" and weight >= 0, "weight must be 0 or more")
total += weight
end
return total
end
---@param sets OxSelectorSet | table<string, OxSelectorItem[]>
function OxSelector:constructor(sets)
if type(sets) ~= "table" then
lib.print.error("Invalid sets provided to OxSelector constructor")
end
if lib.table.type(sets) == "array" then
sets = { [DEFAULT_SET] = sets }
end
self.private.totalWeights = {}
self.private.sets = {}
for setName, set in pairs(sets) do
assert(type(set) == "table" and lib.table.type(set) == "array", "Each set must be an array of OxSelectorItem")
assert(#set > 0, "Each set must contain at least one OxSelectorItem")
self.private.totalWeights[setName] = calculateTotalWeight(set)
end
self.private.sets = sets
end
--- Get a random non-weighted item from a specific set
---@param setName? string
---@return OxSelectorItem?
function OxSelector:getRandom(setName)
local set = (setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET]
if not set then return nil end
local item = set[math.random(#set)][2]
return type(item) == "table" and deepClone(item) or item
end
--- Get a random weighted item from a specific set
---@param setName? string
---@return OxSelectorItem?
function OxSelector:getRandomWeighted(setName)
local set = (setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET]
if not set then return nil end
local totalWeight = self.private.totalWeights[setName or DEFAULT_SET]
if totalWeight == 0 then return nil end
local randomWeight = math.random() * totalWeight
local cumulativeWeight = 0
for i = 1, #set do
cumulativeWeight = cumulativeWeight + set[i][1]
if randomWeight <= cumulativeWeight then
local item = set[i][2]
return type(item) == "table" and deepClone(item) or item
end
end
return nil
end
--- get multiple non-weighted random items from a specific set
---@param setName? string
---@param count number
---@return OxSelectorItem[]
function OxSelector:getRandomAmount(setName, count)
assert(type(count) == "number" and count > 0, "Count must be a positive number")
local items = {}
for _ = 1, count do
local item = self:getRandom(setName)
if item then
table.insert(items, item)
end
end
return items
end
--- get multiple weighted random items from a specific set
---@param setName? string
---@param count number
---@return OxSelectorItem[]
function OxSelector:getRandomWeightedAmount(setName, count)
assert(type(count) == "number" and count > 0, "Count must be a positive number")
local items = {}
for _ = 1, count do
local item = self:getRandomWeighted(setName)
if item then
table.insert(items, item)
end
end
return items
end
--- get all items from a specific set
---@param setName? string
---@return OxSelectorItem[]
function OxSelector:getSet(setName)
return deepClone((setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET])
end
--- get all sets
---@return table<string, OxSelectorItem[]>
function OxSelector:getAllSets()
return deepClone(self.private.sets)
end
--- add a new set
---@param setName string
---@param items OxSelectorItem[]
function OxSelector:addSet(setName, items)
assert(type(setName) == "string", "setName must be a string")
if self.private.sets[setName] then
lib.print.error("Selector set '" .. setName .. "' already exists.")
return
end
assert(type(items) == "table" and lib.table.type(items) == "array", "items must be an array")
assert(#items > 0, "set must contain at least one OxSelectorItem")
self.private.totalWeights[setName] = calculateTotalWeight(items)
self.private.sets[setName] = items
end
--- update an existing set
---@param setName string
---@param newItems OxSelectorItem[]
function OxSelector:updateSet(setName, newItems)
assert(type(setName) == "string", "setName must be a string")
if not self.private.sets[setName] then
lib.print.error("Selector set '" .. setName .. "' does not exist.")
return
end
assert(type(newItems) == "table" and lib.table.type(newItems) == "array", "newItems must be an array")
assert(#newItems > 0, "set must contain at least one OxSelectorItem")
self.private.totalWeights[setName] = calculateTotalWeight(newItems)
self.private.sets[setName] = newItems
end
--- remove a set
---@param setName string
function OxSelector:removeSet(setName)
assert(type(setName) == "string", "setName must be a string")
self.private.totalWeights[setName] = nil
self.private.sets[setName] = nil
end
lib.selector = OxSelector
return lib.selector

View File

@@ -47,13 +47,10 @@ 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 tabletype1 = table.type(t1) local type1, type2 = type(t1), type(t2)
if not tabletype1 then return t1 == t2 end if type1 ~= type2 then return false end
if type1 ~= 'table' and type2 ~= 'table' then return t1 == t2 end
if tabletype1 ~= table.type(t2) or (tabletype1 == 'array' and #t1 ~= #t2) then
return false
end
for k, v1 in pairs(t1) do for k, v1 in pairs(t1) do
local v2 = t2[k] local v2 = t2[k]

View File

@@ -345,6 +345,8 @@ local function setZone(data)
data.contains = data.contains or contains data.contains = data.contains or contains
if lib.context == 'client' then if lib.context == 'client' then
local coords = cache.coords or GetEntityCoords(cache.ped)
data.distance = #(data.coords - coords)
data.setDebug = setDebug data.setDebug = setDebug
if data.debug then if data.debug then

View File

@@ -14,3 +14,4 @@ export * from './vehicleProperties';
export * from './callback'; export * from './callback';
export * from './points'; export * from './points';
export * from './dui'; export * from './dui';
export * from './addKeybind';

View File

@@ -1,7 +1,7 @@
{ {
"name": "@communityox/ox_lib", "name": "@communityox/ox_lib",
"author": "Overextended", "author": "Overextended",
"version": "3.31.2", "version": "3.32.2",
"description": "JS/TS wrapper for ox_lib exports", "description": "JS/TS wrapper for ox_lib exports",
"main": "./shared/index.js", "main": "./shared/index.js",
"types": "./shared/index.d.ts", "types": "./shared/index.d.ts",

View File

@@ -11,6 +11,7 @@ local DisableControlAction = DisableControlAction
local DisablePlayerFiring = DisablePlayerFiring local DisablePlayerFiring = DisablePlayerFiring
local playerState = LocalPlayer.state local playerState = LocalPlayer.state
local createdProps = {} local createdProps = {}
local maxProps = GetConvarInt('ox:progressPropLimit', 2)
---@class ProgressPropProps ---@class ProgressPropProps
---@field model string ---@field model string
@@ -34,13 +35,16 @@ local createdProps = {}
---@field disable? { move?: boolean, sprint?: boolean, car?: boolean, combat?: boolean, mouse?: boolean } ---@field disable? { move?: boolean, sprint?: boolean, car?: boolean, combat?: boolean, mouse?: boolean }
local function createProp(ped, prop) local function createProp(ped, prop)
lib.requestModel(prop.model) local ok, result = pcall(lib.requestModel, prop.model)
if not ok then return lib.print.error(result) end
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(result, 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, 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) true, false, true, prop.rotOrder or 0, true)
SetModelAsNoLongerNeeded(prop.model) SetModelAsNoLongerNeeded(result)
return object return object
end end
@@ -90,7 +94,7 @@ local function startProgress(data)
end end
if data.prop then if data.prop then
playerState:set('lib:progressProps', data.prop, true) TriggerServerEvent('ox_lib:progressProps', data.prop)
end end
local disable = data.disable local disable = data.disable
@@ -137,7 +141,7 @@ local function startProgress(data)
end end
if data.prop then if data.prop then
playerState:set('lib:progressProps', nil, true) TriggerServerEvent('ox_lib:progressProps', nil)
end end
if anim then if anim then
@@ -225,14 +229,18 @@ end
local function deleteProgressProps(serverId) local function deleteProgressProps(serverId)
local playerProps = createdProps[serverId] local playerProps = createdProps[serverId]
if not playerProps then return end if not playerProps then return end
createdProps[serverId] = nil
for i = 1, #playerProps do for i = 1, #playerProps do
local prop = playerProps[i] local prop = playerProps[i]
if DoesEntityExist(prop) then if DoesEntityExist(prop) then
DeleteEntity(prop) DeleteEntity(prop)
end end
end end
createdProps[serverId] = nil
end end
RegisterNetEvent('onPlayerDropped', function(serverId) RegisterNetEvent('onPlayerDropped', function(serverId)
@@ -248,22 +256,29 @@ AddStateBagChangeHandler('lib:progressProps', nil, function(bagName, key, value,
local ped = GetPlayerPed(ply) local ped = GetPlayerPed(ply)
local serverId = GetPlayerServerId(ply) local serverId = GetPlayerServerId(ply)
if not value then if not value or createdProps[serverId] then
return deleteProgressProps(serverId) return deleteProgressProps(serverId)
end end
createdProps[serverId] = {} local playerProps = {}
local playerProps = createdProps[serverId]
if value.model then if value.model then
playerProps[#playerProps + 1] = createProp(ped, value) local prop = createProp(ped, value)
if prop then
playerProps[#playerProps + 1] = prop
end
else else
for i = 1, #value do local propCount = math.min(maxProps, #value)
local prop = value[i]
for i = 1, propCount do
local prop = createProp(ped, value[i])
if prop then if prop then
playerProps[#playerProps + 1] = createProp(ped, prop) playerProps[#playerProps + 1] = prop
end end
end end
end end
createdProps[serverId] = playerProps
end) end)

View File

@@ -0,0 +1,12 @@
local maxProps = GetConvarInt('ox:progressPropLimit', 2)
---@param props ProgressPropProps | ProgressPropProps[] | nil
RegisterNetEvent('ox_lib:progressProps', function(props)
if type(props) == 'table' then
props = #props > maxProps and { table.unpack(props, 1, maxProps) } or props
else
props = nil
end
Player(source).state:set('lib:progressProps', props, true)
end)