18 Commits

Author SHA1 Message Date
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
github-actions
88c68c7068 chore: bump version to v3.31.2 2025-08-18 11:45:57 +00:00
PaPi
1e178180bb chore(locales): update fr.json (#38) 2025-08-18 13:45:14 +02:00
Zoo
fef441c456 feat(package/addKeybind): add keybind to ts package (#39) 2025-08-18 13:30:43 +02:00
github-actions
85a3879a03 chore: bump version to v3.31.1 2025-08-12 14:45:04 +00:00
mikigoalie
65813e75e6 feat(imports/table): add table.map (#35) 2025-08-12 16:42:57 +02:00
David Malchin
a9648fb45f fix(points): grid inconsistencies (#37) 2025-08-10 02:20:46 +02:00
12 changed files with 238 additions and 44 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.0' version '3.32.1'
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 = {}

View File

@@ -39,10 +39,14 @@ local function removePoint(self)
points[self.id] = nil points[self.id] = nil
end end
local function hasRemovePoint(entry)
return entry.remove == removePoint
end
CreateThread(function() CreateThread(function()
while true do while true do
local coords = GetEntityCoords(cache.ped) local coords = GetEntityCoords(cache.ped)
local newPoints = lib.grid.getNearbyEntries(coords, function(entry) return entry.remove == removePoint end) --[[@as CPoint[] ]] local newPoints = lib.grid.getNearbyEntries(coords, hasRemovePoint) --[[@as CPoint[] ]]
local cellX, cellY = lib.grid.getCellPosition(coords) local cellX, cellY = lib.grid.getCellPosition(coords)
cache.coords = coords cache.coords = coords
closestPoint = nil closestPoint = nil
@@ -89,9 +93,11 @@ CreateThread(function()
nearbyCount += 1 nearbyCount += 1
nearbyPoints[nearbyCount] = point nearbyPoints[nearbyCount] = point
if point.onEnter and not point.inside then if not point.inside then
point.inside = true point.inside = true
point:onEnter() if point.onEnter then
point:onEnter()
end
end end
elseif point.currentDistance then elseif point.currentDistance then
if point.onExit then point:onExit() end if point.onExit then point:onExit() end

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]
@@ -123,11 +120,24 @@ local function shuffle(tbl)
return tbl return tbl
end end
---@param tbl table
---@param fn function(value: any, key: any): any
---@return table
local function map(tbl, fn)
local result = {}
for k, v in pairs(tbl) do
result[k] = fn(v, k)
end
return result
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 table.shuffle = shuffle
table.map = map
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

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

@@ -7,27 +7,27 @@
"confirm": "Confirmer", "confirm": "Confirmer",
"more": "Plus...", "more": "Plus...",
"settings": { "settings": {
"locale": "Change locale", "locale": "Changer la langue",
"locale_description": "Current language: ${language} (%s)", "locale_description": "Langue actuelle : ${language} (%s)",
"notification_audio": "Notification audio", "notification_audio": "Audio des notifications",
"notification_position": "Notification position" "notification_position": "Position des notifications"
}, },
"position": { "position": {
"bottom": "Bottom", "bottom": "Bas",
"bottom-left": "Bottom-left", "bottom-left": "Bas-gauche",
"bottom-right": "Bottom-right", "bottom-right": "Bas-droite",
"center-left": "Center-left", "center-left": "Centre-gauche",
"center-right": "Center-right", "center-right": "Centre-droite",
"top": "Top", "top": "Haut",
"top-left": "Top-left", "top-left": "Haut-gauche",
"top-right": "Top-right" "top-right": "Haut-droite"
} }
}, },
"open_radial_menu": "Open radial menu", "open_radial_menu": "Ouvrir le menu radial",
"cancel_progress": "Cancel current progress bar", "cancel_progress": "Annuler la barre de progression actuelle",
"txadmin_announcement": "Server announcement by %s", "txadmin_announcement": "Annonce serveur par %s",
"txadmin_dm": "Direct Message from %s", "txadmin_dm": "Message privé de %s",
"txadmin_warn": "You have been warned by %s", "txadmin_warn": "Vous avez été averti par %s",
"txadmin_warn_content": "%s \nAction ID: %s", "txadmin_warn_content": "%s\nID de l'action : %s",
"txadmin_scheduledrestart": "Scheduled Restart" "txadmin_scheduledrestart": "Redémarrage programmé"
} }

View File

@@ -0,0 +1,120 @@
interface CKeybind extends KeybindProps {
currentKey: string;
disabled: boolean;
isPressed: boolean;
hash: number;
getCurrentKey(): string;
isControlPressed(): boolean;
}
interface KeybindProps {
name: string;
description: string;
defaultMapper?: string;
defaultKey?: string;
disabled?: boolean;
disable?(this: CKeybind, toggle: boolean): void;
onPressed?(this: CKeybind): void;
onReleased?(this: CKeybind): void;
[key: string]: any;
}
const keybinds: Record<string, CKeybind> = {};
class Keybind implements CKeybind {
name: string;
description: string;
defaultMapper?: string;
defaultKey?: string;
onPressed?: (this: CKeybind) => void;
onReleased?: (this: CKeybind) => void;
secondaryKey?: string;
secondaryMapper?: string;
[key: string]: any;
disabled: boolean = false;
isPressed: boolean = false;
hash: number;
constructor(data: KeybindProps) {
this.name = data.name;
this.description = data.description;
this.defaultMapper = data.defaultMapper ?? "keyboard";
this.defaultKey = data.defaultKey ?? "";
this.secondaryKey = data.secondaryKey;
this.secondaryMapper = data.secondaryMapper;
if (typeof data.disabled === "boolean") this.disabled = data.disabled;
this.onPressed = data.onPressed;
this.onReleased = data.onReleased;
this.hash = GetHashKey("+" + this.name) | 0x80000000;
}
get currentKey(): string {
return this.getCurrentKey();
}
getCurrentKey(): string {
const label = GetControlInstructionalButton(0, this.hash, true);
return label.substring(2);
}
isControlPressed(): boolean {
return this.isPressed;
}
disable(toggle: boolean): void {
this.disabled = toggle;
}
}
export function addKeybind(data: KeybindProps): CKeybind {
const kb = new Keybind(data);
keybinds[kb.name] = kb;
RegisterCommand("+" + kb.name, () => {
if (kb.disabled || IsPauseMenuActive()) return;
kb.isPressed = true;
kb.onPressed?.call(kb);
}, false);
RegisterCommand("-" + kb.name, () => {
if (kb.disabled || IsPauseMenuActive()) return;
kb.isPressed = false;
kb.onReleased?.call(kb);
}, false);
RegisterKeyMapping(
"+" + kb.name,
kb.description,
kb.defaultMapper ?? "keyboard",
kb.defaultKey ?? ""
);
if (kb.secondaryKey) {
RegisterKeyMapping(
"~!+" + kb.name,
kb.description,
kb.secondaryMapper ?? kb.defaultMapper ?? "keyboard",
kb.secondaryKey
);
}
setTimeout(() => {
emit("chat:removeSuggestion", `/+${kb.name}`);
emit("chat:removeSuggestion", `/-${kb.name}`);
}, 500);
return kb;
}
export function getKeybind(name: string): CKeybind | undefined {
return keybinds[name];
}
export function getAllKeybinds(): Readonly<Record<string, CKeybind>> {
return keybinds;
}

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.0", "version": "3.32.1",
"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)