34 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
github-actions
3c405250b0 chore: bump version to v3.30.9 2025-05-18 13:25:19 +00:00
ANTOND.
7375e4054d docs: update cfxlua extension url (#16) 2025-05-18 14:45:54 +02:00
ANTOND.
22b3600a14 fix(web): re-apply patch for react-hot-toast (#13) 2025-05-18 14:32:22 +02:00
29 changed files with 781 additions and 321 deletions

View File

@@ -27,6 +27,6 @@ https://www.npmjs.com/package/@communityox/ox_lib
## 🖥️ Lua Language Server
- Install [Lua Language Server](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) to ease development with annotations, type checking, diagnostics, and more.
- Install [cfxlua-vscode](https://marketplace.visualstudio.com/items?itemName=communityox.cfxlua-vscode) to add natives and cfxlua runtime declarations to LLS.
- Install [CfxLua IntelliSense](https://marketplace.visualstudio.com/items?itemName=communityox.cfxlua-vscode-cox) to add natives and cfxlua runtime declarations to LLS.
- You can load ox_lib into your global development environment by modifying workspace/user settings "Lua.workspace.library" with the resource path.
- e.g. "c:/fxserver/resources/ox_lib"

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

View File

@@ -84,6 +84,29 @@ function lib.dui:sendMessage(message)
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)
if cache.resource ~= resourceName then return end

View File

@@ -94,6 +94,7 @@ end
if service == 'fivemanage' then
local key = GetConvar('fivemanage:key', '')
local dataset = GetConvar('fivemanage:dataset', '')
if key ~= '' then
local endpoint = 'https://api.fivemanage.com/api/logs/batch'
@@ -101,9 +102,13 @@ if service == 'fivemanage' then
local headers = {
['Content-Type'] = 'application/json',
['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, ...)
if not buffer then
buffer = {}
@@ -123,17 +128,43 @@ if service == 'fivemanage' then
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
buffer[bufferSize] = {
level = "info",
message = message,
resource = cache.resource,
metadata = {
hostname = hostname,
service = event,
source = source,
tags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil),
}
metadata = metadata,
}
end
end

View File

@@ -39,10 +39,14 @@ local function removePoint(self)
points[self.id] = nil
end
local function hasRemovePoint(entry)
return entry.remove == removePoint
end
CreateThread(function()
while true do
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)
cache.coords = coords
closestPoint = nil
@@ -89,9 +93,11 @@ CreateThread(function()
nearbyCount += 1
nearbyPoints[nearbyCount] = point
if point.onEnter and not point.inside then
if not point.inside then
point.inside = true
point:onEnter()
if point.onEnter then
point:onEnter()
end
end
elseif point.currentDistance then
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
---Compares if two values are equal, iterating over tables and matching both keys and values.
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 tabletype1 ~= table.type(t2) or (tabletype1 == 'array' and #t1 ~= #t2) then
return false
end
if type1 ~= type2 then return false end
if type1 ~= 'table' and type2 ~= 'table' then return t1 == t2 end
for k, v1 in pairs(t1) do
local v2 = t2[k]
@@ -123,11 +120,24 @@ local function shuffle(tbl)
return tbl
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.matches = table_matches
table.deepclone = table_deepclone
table.merge = table_merge
table.shuffle = shuffle
table.map = map
local frozenNewIndex = function(self) error(('cannot set values on a frozen table (%s)'):format(self), 2) end
local _rawset = rawset

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
"": {
"name": "@communityox/ox_lib",
"dependencies": {
"@nativewrappers/client": "^1.7.33",
"@nativewrappers/fivem": "^0.0.103",
"csstype": "^3.1.3",
"fast-printf": "^1.6.9",
"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=="],
"@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=="],

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 './points';
export * from './dui';
export * from './addKeybind';

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{
"name": "@communityox/ox_lib",
"author": "Overextended",
"version": "3.30.8",
"version": "3.32.2",
"description": "JS/TS wrapper for ox_lib exports",
"main": "./shared/index.js",
"types": "./shared/index.d.ts",
@@ -28,7 +28,7 @@
},
"license": "LGPL-3.0",
"dependencies": {
"@nativewrappers/client": "^1.7.33",
"@nativewrappers/fivem": "^0.0.103",
"csstype": "^3.1.3",
"fast-printf": "^1.6.9",
"typescript": "^5.4.2"

View File

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

View File

@@ -29,40 +29,43 @@ local GetCurrentPedWeapon = GetCurrentPedWeapon
CreateThread(function()
while true do
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
if vehicle ~= cache.vehicle then
local vehicle = GetVehiclePedIsIn(ped, false)
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)
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
if cache.game == 'redm' then
local mount = GetMount(ped)
local onMount = IsPedOnMount(ped)
cache:set('mount', onMount and mount or false)
end
else
cache:set('vehicle', false)
cache:set('seat', false)
local hasWeapon, currentWeapon = GetCurrentPedWeapon(ped, true)
cache:set('weapon', (hasWeapon and currentWeapon ~= 0) and currentWeapon or false)
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)
end
end)

View File

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

View File

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

@@ -21,7 +21,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.41.3",
"react-hot-toast": "^2.4.0",
"react-hot-toast": "^2.5.2",
"react-markdown": "^8.0.1",
"remark-gfm": "^3.0.1",
"tinycolor2": "^1.6.0",
@@ -46,6 +46,9 @@
},
},
},
"patchedDependencies": {
"react-hot-toast@2.5.2": "patches/react-hot-toast@2.5.2.patch",
},
"packages": {
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],

View File

@@ -21,7 +21,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.41.3",
"react-hot-toast": "^2.4.0",
"react-hot-toast": "^2.5.2",
"react-markdown": "^8.0.1",
"remark-gfm": "^3.0.1",
"tinycolor2": "^1.6.0",
@@ -50,9 +50,7 @@
"prop-types": "^15.8.1",
"rimraf": "^3.0.2"
},
"pnpm": {
"patchedDependencies": {
"react-hot-toast@2.4.0": "patches/react-hot-toast@2.4.0.patch"
}
"patchedDependencies": {
"react-hot-toast@2.5.2": "patches/react-hot-toast@2.5.2.patch"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@@ -24,6 +24,8 @@ const TextareaField: React.FC<Props> = (props) => {
autosize={props.row.autosize}
minRows={props.row.min}
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%)' };
} else if (position === 'top-center') {
animation = { from: 'Y(0px)', to: 'Y(-100%)' };
} else if (position === 'bottom') {
} else if (position === 'bottom-center') {
animation = { from: 'Y(0px)', to: 'Y(100%)' };
} else {
animation = { from: 'X(0px)', to: 'X(100%)' };
@@ -138,7 +138,7 @@ const Notifications: React.FC = () => {
} else {
iconColor = tinycolor(data.iconColor).toRgbString();
}
toast.custom(
(t) => (
<Box

View File

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