31 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
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
github-actions
dec6caa63b chore: bump version to v3.31.0 2025-07-26 17:59:55 +00:00
mikigoalie
967682c449 fix(web/notifications): bottom center position (#33) 2025-07-26 16:07:52 +02:00
Luca
301ff2daf8 feat(interface/input): add size to input dialog options (#32) 2025-07-26 14:34:01 +02:00
ANTOND.
fd8d65166b fix(interface/input): add min and max length props to input type (#31) 2025-07-23 17:10:57 +02:00
ANTOND.
581640c07a feat(web/dialog/textarea): add min and max length (#30) 2025-07-15 21:51:52 +02:00
Mustafa Burak Güneş
9fbf0222ad fix(resource/cache): skip cache updates during ped transitions (#28)
also fixes an issue with cache not being set correctly when weapon hash is a negative number
2025-07-09 18:59:19 +02:00
Laica Lunasys
753d3eb025 chore: use nativewrappers/fivem instead of client (#25) 2025-06-30 15:59:34 +02:00
andreutu
815caddb02 fix(client/interface): add missing warning notify type in npm package (#23) 2025-06-08 15:01:32 +02:00
github-actions
3c5d5faa9f chore: bump version to v3.30.10 2025-05-29 11:17:05 +00:00
uShifty
ae640729ca fix(shared): Improper import of context (#18) 2025-05-27 11:18:45 +02:00
24 changed files with 492 additions and 85 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.30.9' 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

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

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]
@@ -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

@@ -4,7 +4,7 @@
"": { "": {
"name": "@communityox/ox_lib", "name": "@communityox/ox_lib",
"dependencies": { "dependencies": {
"@nativewrappers/client": "^1.7.33", "@nativewrappers/fivem": "^0.0.103",
"csstype": "^3.1.3", "csstype": "^3.1.3",
"fast-printf": "^1.6.9", "fast-printf": "^1.6.9",
"typescript": "^5.4.2", "typescript": "^5.4.2",
@@ -97,7 +97,7 @@
"@mantine/utils": ["@mantine/utils@6.0.22", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-RSKlNZvxhMCkOFZ6slbYvZYbWjHUM+PxDQnupIOxIdsTZQQjx/BFfrfJ7kQFOP+g7MtpOds8weAetEs5obwMOQ=="], "@mantine/utils": ["@mantine/utils@6.0.22", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-RSKlNZvxhMCkOFZ6slbYvZYbWjHUM+PxDQnupIOxIdsTZQQjx/BFfrfJ7kQFOP+g7MtpOds8weAetEs5obwMOQ=="],
"@nativewrappers/client": ["@nativewrappers/client@1.7.33", "", {}, "sha512-phuBBGdDPxZiZyw5CaFs1XWfvllnEtwATMdLaNucwMofVg/O/FjlP1bTUq4SOm4qhSZ4Zdo351ijHzBSIbZs6g=="], "@nativewrappers/fivem": ["@nativewrappers/fivem@0.0.103", "", {}, "sha512-x0W00Mx9ZN/rTS9XZc5Kf1hjahqRmlo9sPiuJP4kCYeQG4LDJyglXCsHcfNfygGq6WEblG1W2FLgm4MGDn/wHA=="],
"@radix-ui/number": ["@radix-ui/number@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA=="], "@radix-ui/number": ["@radix-ui/number@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA=="],

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

@@ -10,7 +10,7 @@ type NotificationPosition =
| 'bottom-left' | 'bottom-left'
| 'center-right' | 'center-right'
| 'center-left'; | 'center-left';
type NotificationType = 'inform' | 'error' | 'success'; type NotificationType = 'inform' | 'error' | 'success' | 'warning';
type IconAnimation = 'spin' | 'spinPulse' | 'spinReverse' | 'pulse' | 'beat' | 'fade' | 'beatFade' | 'bounce' | 'shake'; type IconAnimation = 'spin' | 'spinPulse' | 'spinReverse' | 'pulse' | 'beat' | 'fade' | 'beatFade' | 'bounce' | 'shake';
interface NotifyProps { interface NotifyProps {

View File

@@ -1,4 +1,4 @@
import { Vector3 } from '@nativewrappers/client'; import { Vector3 } from '@nativewrappers/fivem';
import { cache } from '../cache'; import { cache } from '../cache';
let points: Point[] = []; let points: Point[] = [];

View File

@@ -1,7 +1,7 @@
{ {
"name": "@communityox/ox_lib", "name": "@communityox/ox_lib",
"author": "Overextended", "author": "Overextended",
"version": "3.30.9", "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",
@@ -28,7 +28,7 @@
}, },
"license": "LGPL-3.0", "license": "LGPL-3.0",
"dependencies": { "dependencies": {
"@nativewrappers/client": "^1.7.33", "@nativewrappers/fivem": "^0.0.103",
"csstype": "^3.1.3", "csstype": "^3.1.3",
"fast-printf": "^1.6.9", "fast-printf": "^1.6.9",
"typescript": "^5.4.2" "typescript": "^5.4.2"

View File

@@ -1,6 +1,6 @@
import { cache } from '../cache/index'; import { cache } from '../cache/index';
import { context } from 'shared'; import { context } from '../../index';
import { Vector3 } from "@nativewrappers/client"; import { Vector3 } from "@nativewrappers/fivem";
interface NearbyVehicle { interface NearbyVehicle {
vehicle: number; vehicle: number;

View File

@@ -29,40 +29,43 @@ local GetCurrentPedWeapon = GetCurrentPedWeapon
CreateThread(function() CreateThread(function()
while true do while true do
local ped = PlayerPedId() local ped = PlayerPedId()
cache:set('ped', ped)
local vehicle = GetVehiclePedIsIn(ped, false) -- If the player's ped is changed, the ped value may return 0 during the transition. If the value is 0, skip all checks.
if ped ~= 0 then
cache:set('ped', ped)
if vehicle > 0 then local vehicle = GetVehiclePedIsIn(ped, false)
if vehicle ~= cache.vehicle then
if vehicle > 0 then
if vehicle ~= cache.vehicle then
cache:set('seat', false)
end
cache:set('vehicle', vehicle)
if not cache.seat or GetPedInVehicleSeat(vehicle, cache.seat) ~= ped then
for i = -1, GetVehicleMaxNumberOfPassengers(vehicle) - 1 do
if GetPedInVehicleSeat(vehicle, i) == ped then
cache:set('seat', i)
break
end
end
end
else
cache:set('vehicle', false)
cache:set('seat', false) cache:set('seat', false)
end end
cache:set('vehicle', vehicle) if cache.game == 'redm' then
local mount = GetMount(ped)
if not cache.seat or GetPedInVehicleSeat(vehicle, cache.seat) ~= ped then local onMount = IsPedOnMount(ped)
for i = -1, GetVehicleMaxNumberOfPassengers(vehicle) - 1 do cache:set('mount', onMount and mount or false)
if GetPedInVehicleSeat(vehicle, i) == ped then
cache:set('seat', i)
break
end
end
end end
else
cache:set('vehicle', false) local hasWeapon, currentWeapon = GetCurrentPedWeapon(ped, true)
cache:set('seat', false) cache:set('weapon', (hasWeapon and currentWeapon ~= 0) and currentWeapon or false)
end end
if cache.game == 'redm' then
local mount = GetMount(ped)
local onMount = IsPedOnMount(ped)
cache:set('mount', onMount and mount or false)
end
local hasWeapon, currentWeapon = GetCurrentPedWeapon(ped, true)
cache:set('weapon', hasWeapon and currentWeapon or false)
Wait(100) Wait(100)
end end
end) end)

View File

@@ -30,9 +30,12 @@ local input
---@field searchable? boolean ---@field searchable? boolean
---@field description? string ---@field description? string
---@field maxSelectedValues? number ---@field maxSelectedValues? number
---@field minLength? number
---@field maxLength? number
---@class InputDialogOptionsProps ---@class InputDialogOptionsProps
---@field allowCancel? boolean ---@field allowCancel? boolean
---@field size? 'xs' | 'sm' | 'md' | 'lg' | 'xl'
---@param heading string ---@param heading string
---@param rows string[] | InputDialogRowProps[] ---@param rows string[] | InputDialogRowProps[]

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)

View File

@@ -26,6 +26,7 @@ export const debugCustomNotification = () => {
title: 'Error', title: 'Error',
description: 'Notification description', description: 'Notification description',
type: 'error', type: 'error',
position: "bottom"
}, },
}, },
]); ]);

View File

@@ -103,7 +103,7 @@ const InputDialog: React.FC = () => {
centered centered
closeOnEscape={fields.options?.allowCancel !== false} closeOnEscape={fields.options?.allowCancel !== false}
closeOnClickOutside={false} closeOnClickOutside={false}
size="xs" size={fields.options?.size || 'xs'}
styles={{ title: { textAlign: 'center', width: '100%', fontSize: 18 } }} styles={{ title: { textAlign: 'center', width: '100%', fontSize: 18 } }}
title={fields.heading} title={fields.heading}
withCloseButton={false} withCloseButton={false}

View File

@@ -24,6 +24,8 @@ const TextareaField: React.FC<Props> = (props) => {
autosize={props.row.autosize} autosize={props.row.autosize}
minRows={props.row.min} minRows={props.row.min}
maxRows={props.row.max} maxRows={props.row.max}
minLength={props.row.minLength}
maxLength={props.row.maxLength}
/> />
); );
}; };

View File

@@ -61,7 +61,7 @@ const getAnimation = (visible: boolean, position: string) => {
animation = { from: 'X(0px)', to: 'X(-100%)' }; animation = { from: 'X(0px)', to: 'X(-100%)' };
} else if (position === 'top-center') { } else if (position === 'top-center') {
animation = { from: 'Y(0px)', to: 'Y(-100%)' }; animation = { from: 'Y(0px)', to: 'Y(-100%)' };
} else if (position === 'bottom') { } else if (position === 'bottom-center') {
animation = { from: 'Y(0px)', to: 'Y(100%)' }; animation = { from: 'Y(0px)', to: 'Y(100%)' };
} else { } else {
animation = { from: 'X(0px)', to: 'X(100%)' }; animation = { from: 'X(0px)', to: 'X(100%)' };

View File

@@ -5,6 +5,7 @@ export interface InputProps {
rows: Array<IInput | ICheckbox | ISelect | INumber | ISlider | IColorInput | IDateInput | ITextarea | ITimeInput>; rows: Array<IInput | ICheckbox | ISelect | INumber | ISlider | IColorInput | IDateInput | ITextarea | ITimeInput>;
options?: { options?: {
allowCancel?: boolean; allowCancel?: boolean;
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
}; };
} }
@@ -76,4 +77,6 @@ export interface ITextarea extends BaseField<'textarea', string> {
autosize?: boolean; autosize?: boolean;
min?: number; min?: number;
max?: number; max?: number;
minLength?: number;
maxLength?: number;
} }