diff --git a/README.md b/README.md
index 02479fcf8..9142ea5b8 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,27 @@
-
-
+# ox_lib
-## ox_lib
+A FiveM library and resource implementing reusable modules, methods, and UI elements.
-Cfx provides functionality for loading external files in a resource through fxmanifest.
+![](https://img.shields.io/github/downloads/overextended/ox_lib/total?logo=github)
+![](https://img.shields.io/github/downloads/overextended/ox_lib/latest/total?logo=github)
+![](https://img.shields.io/github/contributors/overextended/ox_lib?logo=github)
+![](https://img.shields.io/github/v/release/overextended/ox_lib?logo=github)
-```lua
-lua54 'yes'
-shared_script '@ox_lib/init.lua'
-```
+## 📚 Documentation
-### server.cfg
+https://overextended.dev/ox_lib
-```
-add_ace resource.ox_lib command.add_ace allow
-add_ace resource.ox_lib command.remove_ace allow
-add_ace resource.ox_lib command.add_principal allow
-add_ace resource.ox_lib command.remove_principal allow
-```
+## 💾 Download
-## License
+https://github.com/overextended/ox_lib/releases/latest/download/ox_lib.zip
-LGPL-3.0-or-later
+## npm Package
+
+https://www.npmjs.com/package/@overextended/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=overextended.cfxlua-vscode) 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"
+ - e.g. "c:/fxserver/resources/ox_lib"
diff --git a/fxmanifest.lua b/fxmanifest.lua
index 456beb281..fcb567a07 100644
--- a/fxmanifest.lua
+++ b/fxmanifest.lua
@@ -8,7 +8,7 @@ rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aw
--[[ Resource Information ]]--
name 'ox_lib'
author 'Overextended'
-version '3.6.0'
+version '3.7.2'
license 'LGPL-3.0-or-later'
repository 'https://github.com/overextended/ox_lib'
description 'A library of shared functions to utilise in other resources.'
diff --git a/imports/addCommand/server.lua b/imports/addCommand/server.lua
index 6479115a3..0b57824fd 100644
--- a/imports/addCommand/server.lua
+++ b/imports/addCommand/server.lua
@@ -6,7 +6,7 @@
---@class OxCommandProperties
---@field help string?
----@field params OxCommandParams[]
+---@field params OxCommandParams[]?
---@field restricted boolean | string | string[]?
---@type OxCommandProperties[]
@@ -25,7 +25,7 @@ end)
---@param source number
---@param args table
---@param raw string
----@param params OxCommandParams[]
+---@param params OxCommandParams[]?
---@return table?
local function parseArguments(source, args, raw, params)
if not params then return args end
diff --git a/imports/cron/server.lua b/imports/cron/server.lua
index 58902c231..c4ef4e2b0 100644
--- a/imports/cron/server.lua
+++ b/imports/cron/server.lua
@@ -1,5 +1,6 @@
lib.cron = {}
-local currentDate = os.date('*t') --[[@as osdate]]
+
+local currentDate = os.date('*t') --[[@as { year: number, month: number, day: number, hour: number, min: number, sec: number, wday: number, yday: number, isdst: boolean }]]
currentDate.sec = 0
---@class OxTaskProperties
@@ -7,6 +8,7 @@ currentDate.sec = 0
---@field hour? number | string
---@field day? number | string
---@field month? number | string
+---@field year? number | string
---@field weekday? number | string
---@field job fun(task: OxTask, date: osdate)
---@field isActive boolean
@@ -26,14 +28,22 @@ local maxUnits = {
month = 12,
}
+--- Gets the amount of days in certain month
+---@param month number
+---@param year? number
+---@return number
+local function getMaxDaysInMonth(month, year)
+ return os.date('*t', os.time({ year = year or currentDate.year, month = month + 1, day = -1 })).day --[[@as number]]
+end
+
---@param value string | number | nil
---@param unit string
---@return string | number | false | nil
local function getTimeUnit(value, unit)
local currentTime = currentDate[unit]
+ local unitMax = maxUnits[unit]
if type(value) == 'string' then
- local unitMax = maxUnits[unit]
local stepValue = string.match(value, '*/(%d+)')
if stepValue then
@@ -55,9 +65,15 @@ local function getTimeUnit(value, unit)
local min, max = string.strsplit('-', range)
min, max = tonumber(min, 10), tonumber(max, 10)
- if currentTime >= min and currentTime <= max then return currentTime end
+ if unit == 'min' then
+ if currentTime >= max then
+ return min + unitMax
+ end
+ elseif currentTime > max then
+ return min + unitMax
+ end
- return min
+ return currentTime < min and min or currentTime
end
local list = string.match(value, '%d+,%d+')
@@ -66,20 +82,32 @@ local function getTimeUnit(value, unit)
for listValue in string.gmatch(value, '%d+') --[[@as number]] do
listValue = tonumber(listValue)
- -- if current minute is less than in the expression 0,10,20,45 * * * *
- if listValue > currentTime then
+ -- e.g. if current time is less than in the expression 0,10,20,45 * * * *
+ if unit == 'min' then
+ if currentTime < listValue then
+ return listValue
+ end
+ elseif currentTime <= listValue then
return listValue
end
end
-- if iterator failed, return the first value in the list
- return tonumber(string.match(value, '%d+'))
+ return tonumber(string.match(value, '%d+')) + unitMax
end
return false
end
- return value or currentTime
+ if value then
+ if unit == 'min' then
+ return value <= currentTime and value + unitMax or value
+ end
+
+ return value < currentTime and value + unitMax or value
+ end
+
+ return currentTime
end
---Get a timestamp for the next time to run the task today.
@@ -89,6 +117,12 @@ function OxTask:getNextTime()
local day = getTimeUnit(self.day, 'day')
+ -- If current day is the last day of the month, and the task is scheduled for the last day of the month, then the task should run.
+ if day == 0 then
+ -- Should probably be used month from getTimeUnit, but don't want to reorder this code.
+ day = getMaxDaysInMonth(currentDate.month)
+ end
+
if day ~= currentDate.day then return end
local month = getTimeUnit(self.month, 'month')
@@ -107,9 +141,16 @@ function OxTask:getNextTime()
if not hour then return end
+ if minute >= 60 then
+ minute = 0
+ hour += 1
+ end
+
+ if hour >= 24 and day then return end
+
return os.time({
- min = minute < 60 and minute or 0,
- hour = hour < 24 and hour or 0,
+ min = minute,
+ hour = hour,
day = day or currentDate.day,
month = month or currentDate.month,
year = currentDate.year,
@@ -120,28 +161,32 @@ end
---@return number
function OxTask:getAbsoluteNextTime()
local minute = getTimeUnit(self.minute, 'min')
-
local hour = getTimeUnit(self.hour, 'hour')
-
local day = getTimeUnit(self.day, 'day')
+ local month = getTimeUnit(self.month, 'month')
+ local year = getTimeUnit(self.year, 'year')
-- To avoid modifying getTimeUnit function, the day is adjusted here if needed.
if self.day then
if currentDate.hour < hour or (currentDate.hour == hour and currentDate.min < minute) then
day = day - 1
- end
- else
+ if day < 1 then
+ day = getMaxDaysInMonth(currentDate.month)
+ end
+ end
+
if currentDate.hour > hour or (currentDate.hour == hour and currentDate.min >= minute) then
day = day + 1
+ if day > getMaxDaysInMonth(currentDate.month) or day == 1 then
+ day = 1
+ month = month + 1
+ end
end
end
-
- local month = getTimeUnit(self.month, 'month')
-
- local year = getTimeUnit(self.year, 'year')
-- Check if time will be in next year.
- if os.time({year=year, month=month, day=day, hour=hour, min=minute}) < os.time() then
+ ---@diagnostic disable-next-line: assign-type-mismatch
+ if os.time({ year = year, month = month, day = day, hour = hour, min = minute }) < os.time() then
year = year and year + 1 or currentDate.year + 1
end
@@ -172,27 +217,28 @@ function OxTask:scheduleTask()
return self:stop()
end
- if self.hour then
- sleep += 86400
- elseif self.minute then
- sleep += 3600
- end
-
- if sleep < 0 then
- sleep += 60
- runAt += 60
- end
+ sleep += 60
end
if self.debug then
- print(('running task %s in %d seconds (%0.2f minutes or %0.2f hours)'):format(self.id, sleep, sleep / 60, sleep / 60 / 60))
+ print(('running task %s in %d seconds (%0.2f minutes or %0.2f hours)'):format(self.id, sleep, sleep / 60,
+ sleep / 60 / 60))
end
- if sleep > 0 then Wait(sleep * 1000) end
+ if sleep > 0 then
+ Wait(sleep * 1000)
+ else -- will this even happen?
+ Wait(1000)
+ return true
+ end
if self.isActive then
self:job(currentDate)
+ if self.debug then
+ print(('(%s/%s/%s %s:%s) ran task %s'):format(currentDate.year, currentDate.month, currentDate.day, currentDate.hour, currentDate.min, self.id))
+ end
+
Wait(30000)
return true
@@ -260,8 +306,8 @@ end
---@param expression string A cron expression such as `* * * * *` representing minute, hour, day, month, and day of the week.
---@param job fun(task: OxTask, date: osdate)
---@param options? { debug?: boolean }
----Creates a new [cronjob](https://en.wikipedia.org/wiki/Cron), scheduling a task to run at fixed times or intervals.
----Supports numbers, any value `*`, lists `1,2,3`, ranges `1-3`, and steps `*/4`.
+---Creates a new [cronjob](https://en.wikipedia.org/wiki/Cron), scheduling a task to run at fixed times or intervals.
+---Supports numbers, any value `*`, lists `1,2,3`, ranges `1-3`, and steps `*/4`.
---Day of the week is a range of `1-7` starting from Sunday and allows short-names (i.e. sun, mon, tue).
function lib.cron.new(expression, job, options)
if not job or type(job) ~= 'function' then return end
diff --git a/imports/getClosestPed/client.lua b/imports/getClosestPed/client.lua
index bbde2e9fb..38e6053f2 100644
--- a/imports/getClosestPed/client.lua
+++ b/imports/getClosestPed/client.lua
@@ -16,7 +16,7 @@ function lib.getClosestPed(coords, maxDistance)
if distance < maxDistance then
maxDistance = distance
- closestObject = ped
+ closestPed = ped
closestCoords = pedCoords
end
end
diff --git a/imports/getNearbyObjects/client.lua b/imports/getNearbyObjects/client.lua
index d7ffc4e7a..0bd2e7648 100644
--- a/imports/getNearbyObjects/client.lua
+++ b/imports/getNearbyObjects/client.lua
@@ -1,6 +1,6 @@
---@param coords vector3 The coords to check from.
---@param maxDistance number The max distance to check.
----@return table objects
+---@return { object: number, coords: vector3 }[]
function lib.getNearbyObjects(coords, maxDistance)
local objects = GetGamePool('CObject')
local nearby = {}
@@ -21,7 +21,7 @@ function lib.getNearbyObjects(coords, maxDistance)
}
end
end
-
+
return nearby
end
diff --git a/imports/getNearbyPeds/client.lua b/imports/getNearbyPeds/client.lua
index 7c296f7f6..03424b93a 100644
--- a/imports/getNearbyPeds/client.lua
+++ b/imports/getNearbyPeds/client.lua
@@ -1,6 +1,6 @@
---@param coords vector3 The coords to check from.
---@param maxDistance number The max distance to check.
----@return table peds
+---@return { ped: number, coords: vector3 }[]
function lib.getNearbyPeds(coords, maxDistance)
local peds = GetGamePool('CPed')
local nearby = {}
diff --git a/imports/getNearbyPlayers/client.lua b/imports/getNearbyPlayers/client.lua
index 125469732..4438e9570 100644
--- a/imports/getNearbyPlayers/client.lua
+++ b/imports/getNearbyPlayers/client.lua
@@ -1,7 +1,7 @@
---@param coords vector3 The coords to check from.
---@param maxDistance number The max distance to check.
---@param includePlayer boolean? Whether or not to include the current player.
----@return table players
+---@return { id: number, ped: number, coords: vector3 }[]
function lib.getNearbyPlayers(coords, maxDistance, includePlayer)
local players = GetActivePlayers()
local nearby = {}
diff --git a/imports/getNearbyVehicles/client.lua b/imports/getNearbyVehicles/client.lua
index 2a62be49d..658493c2e 100644
--- a/imports/getNearbyVehicles/client.lua
+++ b/imports/getNearbyVehicles/client.lua
@@ -1,7 +1,7 @@
---@param coords vector3 The coords to check from.
---@param maxDistance number The max distance to check.
---@param includePlayerVehicle boolean Whether or not to include the player's current vehicle.
----@return table vehicles
+---@return { vehicle: number, coords: vector3 }[]
function lib.getNearbyVehicles(coords, maxDistance, includePlayerVehicle)
local vehicles = GetGamePool('CVehicle')
local nearby = {}
@@ -28,4 +28,4 @@ function lib.getNearbyVehicles(coords, maxDistance, includePlayerVehicle)
return nearby
end
-return lib.getNearbyVehicles
\ No newline at end of file
+return lib.getNearbyVehicles
diff --git a/imports/logger/server.lua b/imports/logger/server.lua
index 5c280b773..1ed17104e 100644
--- a/imports/logger/server.lua
+++ b/imports/logger/server.lua
@@ -1,8 +1,22 @@
local service = GetConvar('ox:logger', 'datadog')
-local hostname = GetConvar('sv_projectName', 'fxserver')
local buffer
local bufferSize = 0
+local function removeColorCodes(str)
+ -- replace ^[0-9] with nothing
+ str = string.gsub(str, "%^%d", "")
+
+ -- replace ^#[0-9A-F]{3,6} with nothing
+ str = string.gsub(str, "%^#[%dA-Fa-f]+", "")
+
+ -- replace ~[a-z]~ with nothing
+ str = string.gsub(str, "~[%a]~", "")
+
+ return str
+end
+
+local hostname = removeColorCodes(GetConvar('ox:logger:hostname', GetConvar('sv_projectName', 'fxserver')))
+
local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
local function base64encode(data)
diff --git a/imports/math/shared.lua b/imports/math/shared.lua
index e4782f1c9..99cf23df4 100644
--- a/imports/math/shared.lua
+++ b/imports/math/shared.lua
@@ -86,8 +86,8 @@ end
---@param upper? boolean
---@return string
function math.tohex(n, upper)
- n = ('0x%x'):format(n)
- return upper and n:upper() or n
+ local formatString = ('0x%s'):format(upper and '%X' or '%x')
+ return formatString:format(n)
end
---Converts input number into grouped digits
@@ -99,4 +99,4 @@ function math.groupdigits(number, seperator) -- credit http://richard.warburton.
return left..(num:reverse():gsub('(%d%d%d)','%1' .. (seperator or ',')):reverse())..right
end
-return lib.math
\ No newline at end of file
+return lib.math
diff --git a/imports/table/shared.lua b/imports/table/shared.lua
index 734414661..5115114c0 100644
--- a/imports/table/shared.lua
+++ b/imports/table/shared.lua
@@ -148,5 +148,4 @@ function table.isfrozen(tbl)
return getmetatable(tbl) == 'readonly'
end
-
return lib.table
diff --git a/imports/zones/server.lua b/imports/zones/server.lua
index 37face35d..01cdb5667 100644
--- a/imports/zones/server.lua
+++ b/imports/zones/server.lua
@@ -46,9 +46,11 @@ lib.zones = {
data.polygon = glm.polygon.new(points)
data.coords = data.polygon:centroid()
+ data.type = 'poly'
data.remove = removeZone
data.contains = contains
data.debug = nil
+ data.debugColour = nil
data.inside = nil
data.onEnter = nil
data.onExit = nil
@@ -70,9 +72,11 @@ lib.zones = {
vec3(-data.size.x, -data.size.y, 0),
vec3(data.size.x, -data.size.y, 0),
}) + data.coords)
+ data.type = 'box'
data.remove = removeZone
data.contains = contains
data.debug = nil
+ data.debugColour = nil
data.inside = nil
data.onEnter = nil
data.onExit = nil
@@ -86,9 +90,11 @@ lib.zones = {
data.id = #Zones + 1
data.coords = convertToVector(data.coords)
data.radius = (data.radius or 2) + 0.0
+ data.type = 'sphere'
data.remove = removeZone
data.contains = insideSphere
data.debug = nil
+ data.debugColour = nil
data.inside = nil
data.onEnter = nil
data.onExit = nil
diff --git a/init.lua b/init.lua
index d2c92d2de..cd05e958e 100644
--- a/init.lua
+++ b/init.lua
@@ -17,6 +17,9 @@ local status = export.hasLoaded()
if status ~= true then error(status, 2) end
+-- Ignore invalid types during msgpack.pack (e.g. userdata)
+msgpack.setoption('ignore_invalid', true)
+
-----------------------------------------------------------------------------------------------
-- Module
-----------------------------------------------------------------------------------------------
diff --git a/locales/hu.json b/locales/hu.json
index 32cec195d..a6426cfe6 100644
--- a/locales/hu.json
+++ b/locales/hu.json
@@ -4,11 +4,11 @@
"cancel": "Mégse",
"close": "Bezárás",
"confirm": "Megerősít",
- "more": "More..."
+ "more": "Tovább..."
},
- "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"
+ "txadmin_announcement": "Szerver bejelentés által %s",
+ "txadmin_dm": "Közvetlen üzenet a %s",
+ "txadmin_warn": "Figyelmeztetett %s",
+ "txadmin_warn_content": "%s \nMűvelet ID: %s",
+ "txadmin_scheduledrestart": "Ütemezett újraindítás"
}
diff --git a/package/client/resource/interface/radial.ts b/package/client/resource/interface/radial.ts
index 9a664957a..dbb6aec34 100644
--- a/package/client/resource/interface/radial.ts
+++ b/package/client/resource/interface/radial.ts
@@ -15,6 +15,8 @@ export const removeRadialItem = (item: string) => exports.ox_lib.removeRadialIte
export const registerRadial = (radial: { id: string; items: Omit[] }) =>
exports.ox_lib.registerRadial(radial);
+export const getCurrentRadialId = () => exports.ox_lib.getCurrentRadialId();
+
export const hideRadial = () => exports.ox_lib.hideRadial();
export const disableRadial = (state: boolean) => exports.ox_lib.disableRadial(state);
diff --git a/package/client/resource/points/index.ts b/package/client/resource/points/index.ts
index 8c4d67556..c41881713 100644
--- a/package/client/resource/points/index.ts
+++ b/package/client/resource/points/index.ts
@@ -6,6 +6,7 @@ let nearbyPoints: Point[] = [];
let nearbyCount: number = 0;
let closestPoint: Point | undefined;
let tick: number | undefined;
+let pointsInterval: CitizenTimer;
interface LibPoint {
coords: number[];
@@ -37,6 +38,9 @@ export class Point {
this.nearby = point.nearby;
this.args = point.args;
points.push(this);
+ if (points.length === 1) {
+ startPointsInterval();
+ }
}
remove = () => {
@@ -51,71 +55,76 @@ export class Point {
}
}
points = points.filter((point) => point.id !== this.id);
+ if (points.length === 0) {
+ clearInterval(pointsInterval);
+ }
};
}
-setInterval(() => {
- if (points.length < 1) return;
+const startPointsInterval = () => {
+ pointsInterval = setInterval(() => {
+ if (points.length < 1) return;
- if (nearbyCount !== 0) {
- nearbyPoints = [];
- nearbyCount = 0;
- }
+ if (nearbyCount !== 0) {
+ nearbyPoints = [];
+ nearbyCount = 0;
+ }
- const coords = Vector3.fromArray(GetEntityCoords(cache.ped, false));
+ const coords = Vector3.fromArray(GetEntityCoords(cache.ped, false));
- if (closestPoint && coords.distance(closestPoint.coords) > closestPoint.distance) {
- closestPoint = undefined;
- }
+ if (closestPoint && coords.distance(closestPoint.coords) > closestPoint.distance) {
+ closestPoint = undefined;
+ }
- for (let i = 0; i < points.length; i++) {
- const point = points[i];
- const distance = coords.distance(point.coords);
+ for (let i = 0; i < points.length; i++) {
+ const point = points[i];
+ const distance = coords.distance(point.coords);
- if (distance <= point.distance) {
- point.currentDistance = distance;
+ if (distance <= point.distance) {
+ point.currentDistance = distance;
- if (closestPoint && closestPoint.currentDistance) {
- if (distance < closestPoint.currentDistance) {
- closestPoint.isClosest = false;
+ if (closestPoint && closestPoint.currentDistance) {
+ if (distance < closestPoint.currentDistance) {
+ closestPoint.isClosest = false;
+ point.isClosest = true;
+ closestPoint = point;
+ }
+ } else if (distance < point.distance) {
point.isClosest = true;
closestPoint = point;
}
- } else if (distance < point.distance) {
- point.isClosest = true;
- closestPoint = point;
- }
- if (point.nearby) {
- nearbyCount++;
- nearbyPoints[nearbyCount - 1] = point;
- }
+ if (point.nearby) {
+ nearbyCount++;
+ nearbyPoints[nearbyCount - 1] = point;
+ }
- if (point.onEnter && !point.inside) {
- point.inside = true;
- point.onEnter();
+ if (point.onEnter && !point.inside) {
+ point.inside = true;
+ point.onEnter();
+ }
+ } else if (point.currentDistance) {
+ if (point.onExit) point.onExit();
+ point.inside = false;
+ point.currentDistance = undefined;
}
- } else if (point.currentDistance) {
- if (point.onExit) point.onExit();
- point.inside = false;
- point.currentDistance = undefined;
}
- }
- if (!tick) {
- if (nearbyCount !== 0) {
- tick = setTick(() => {
- for (let i = 0; i < nearbyCount; i++) {
- const point = nearbyPoints[i];
+ if (!tick) {
+ if (nearbyCount !== 0) {
+ tick = setTick(() => {
+ for (let i = 0; i < nearbyCount; i++) {
+ const point = nearbyPoints[i];
- if (point && point.nearby) {
- point.nearby();
+ if (point && point.nearby) {
+ point.nearby();
+ }
}
- }
- });
+ });
+ }
+ } else if (nearbyCount === 0) {
+ clearTick(tick);
+ tick = undefined;
}
- } else if (nearbyCount === 0) {
- clearTick(tick);
- tick = undefined;
- }
-}, 300);
+ }, 300);
+};
diff --git a/package/client/resource/streaming/index.ts b/package/client/resource/streaming/index.ts
index 37659c577..5af27cc46 100644
--- a/package/client/resource/streaming/index.ts
+++ b/package/client/resource/streaming/index.ts
@@ -41,12 +41,14 @@ export const requestModel = (model: string | number, timeout?: number): Promise<
return streamingRequest(RequestModel, HasModelLoaded, 'model', model, timeout);
};
-export const requestStreamedTextureDict = (textureDict: string, timeout?: number): Promise =>
- streamingRequest(RequestStreamedTextureDict, HasStreamedTextureDictLoaded, 'textureDict', textureDict, timeout);
-
export const requestNamedPtfxAsset = (ptFxName: string, timeout?: number): Promise =>
streamingRequest(RequestNamedPtfxAsset, HasNamedPtfxAssetLoaded, 'ptFxName', ptFxName, timeout);
-export const requestWeaponAsset = (weaponHash: string | number, timeout?: number, weaponResourceFlags: number = 31, extraWeaponComponentFlags: number = 0): Promise => {
- return streamingRequest(RequestWeaponAsset, HasWeaponAssetLoaded, 'weaponHash', weaponHash, timeout, weaponResourceFlags, extraWeaponComponentFlags);
-}
+export const requestScaleformMovie = (scaleformName: string, timeout?: number): Promise =>
+ streamingRequest(RequestScaleformMovie, HasScaleformMovieLoaded, 'scaleformMovie', scaleformName, timeout);
+
+export const requestStreamedTextureDict = (textureDict: string, timeout?: number): Promise =>
+ streamingRequest(RequestStreamedTextureDict, HasStreamedTextureDictLoaded, 'textureDict', textureDict, timeout);
+
+export const requestWeaponAsset = (weaponHash: string | number, timeout?: number, weaponResourceFlags: number = 31, extraWeaponComponentFlags: number = 0): Promise =>
+ streamingRequest(RequestWeaponAsset, HasWeaponAssetLoaded, 'weaponHash', weaponHash, timeout, weaponResourceFlags, extraWeaponComponentFlags);
diff --git a/resource/interface/client/context.lua b/resource/interface/client/context.lua
index 35905fa44..c3e66ea84 100644
--- a/resource/interface/client/context.lua
+++ b/resource/interface/client/context.lua
@@ -6,6 +6,8 @@ local openContextMenu = nil
---@field menu? string
---@field icon? string | {[1]: IconProp, [2]: string};
---@field iconColor? string
+---@field image? string
+---@field progress? number
---@field onSelect? fun(args: any)
---@field arrow? boolean
---@field description? string
diff --git a/resource/interface/client/menu.lua b/resource/interface/client/menu.lua
index eaf75111f..60b95dd66 100644
--- a/resource/interface/client/menu.lua
+++ b/resource/interface/client/menu.lua
@@ -8,9 +8,12 @@ local openMenu
---@class MenuOptions
---@field label string
+---@field progress? number
+---@field colorScheme? string
---@field icon? string | {[1]: IconProp, [2]: string};
----@field checked? boolean
+---@field iconColor? string
---@field values? table
+---@field checked? boolean
---@field description? string
---@field defaultIndex? number
---@field args? {[any]: any}
diff --git a/resource/interface/client/notify.lua b/resource/interface/client/notify.lua
index 2cfb8260d..55584342c 100644
--- a/resource/interface/client/notify.lua
+++ b/resource/interface/client/notify.lua
@@ -1,5 +1,5 @@
---@alias NotificationPosition 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left' | 'center-right' | 'center-left'
----@alias NotificationType 'info' | 'warning' | 'success' | 'error'
+---@alias NotificationType 'inform' | 'warning' | 'success' | 'error'
---@class NotifyProps
---@field id? string
@@ -30,11 +30,11 @@ end
---@param data DefaultNotifyProps
function lib.defaultNotify(data)
- -- Backwards compat for v3
+ -- Backwards compat for v3
data.type = data.status
if data.type == 'inform' then data.type = 'info' end
return lib.notify(data)
end
RegisterNetEvent('ox_lib:notify', lib.notify)
-RegisterNetEvent('ox_lib:defaultNotify', lib.defaultNotify)
\ No newline at end of file
+RegisterNetEvent('ox_lib:defaultNotify', lib.defaultNotify)
diff --git a/resource/vehicleProperties/client.lua b/resource/vehicleProperties/client.lua
index da11ba270..9e09aeb26 100644
--- a/resource/vehicleProperties/client.lua
+++ b/resource/vehicleProperties/client.lua
@@ -1,7 +1,7 @@
if cache.game == 'redm' then return end
---@class VehicleProperties
----@field model? string
+---@field model? number
---@field plate? string
---@field plateIndex? number
---@field bodyHealth? number
@@ -121,7 +121,12 @@ function lib.getVehicleProperties(vehicle)
end
end
- local modLivery = GetVehicleMod(vehicle, 48)
+ local modLiveryCount = GetVehicleLiveryCount(vehicle)
+ local modLivery = GetVehicleLivery(vehicle)
+
+ if modLiveryCount == -1 or modLivery == -1 then
+ modLivery = GetVehicleMod(vehicle, 48)
+ end
local damage = {
windows = {},
@@ -313,6 +318,7 @@ function lib.setVehicleProperties(vehicle, props)
if props.color1 then
if type(props.color1) == 'number' then
+ ClearVehicleCustomPrimaryColour(vehicle)
SetVehicleColours(vehicle, props.color1 --[[@as number]], colorSecondary --[[@as number]])
else
SetVehicleCustomPrimaryColour(vehicle, props.color1[1], props.color1[2], props.color1[3])
@@ -321,6 +327,7 @@ function lib.setVehicleProperties(vehicle, props)
if props.color2 then
if type(props.color2) == 'number' then
+ ClearVehicleCustomPrimaryColour(vehicle)
SetVehicleColours(vehicle, props.color1 or colorPrimary --[[@as number]], props.color2 --[[@as number]])
else
SetVehicleCustomSecondaryColour(vehicle, props.color2[1], props.color2[2], props.color2[3])
diff --git a/resource/version/shared.lua b/resource/version/shared.lua
index db318547c..737b3c4b4 100644
--- a/resource/version/shared.lua
+++ b/resource/version/shared.lua
@@ -1,5 +1,6 @@
function lib.checkDependency(resource, minimumVersion, printMessage)
- local currentVersion = GetResourceMetadata(resource, 'version', 0):match('%d+%.%d+%.%d+')
+ local currentVersion = GetResourceMetadata(resource, 'version', 0)
+ currentVersion = currentVersion and currentVersion:match('%d+%.%d+%.%d+') or 'unknown'
if currentVersion ~= minimumVersion then
local cv = { string.strsplit('.', currentVersion) }
@@ -10,7 +11,7 @@ function lib.checkDependency(resource, minimumVersion, printMessage)
local current, minimum = tonumber(cv[i]), tonumber(mv[i])
if current ~= minimum then
- if current < minimum then
+ if not current or current < minimum then
if printMessage then
return print(msg)
end
diff --git a/resource/zoneCreator/client.lua b/resource/zoneCreator/client.lua
index aef843b6d..5dfee8288 100644
--- a/resource/zoneCreator/client.lua
+++ b/resource/zoneCreator/client.lua
@@ -60,7 +60,7 @@ local function closeCreator(cancel)
points[#points + 1] = vec(xCoord, yCoord)
end
- ---@type string[]
+ ---@type string[]?
local input = lib.inputDialog(('Name your %s Zone'):format(firstToUpper(zoneType)), {
{ type = 'input', label = 'Name', placeholder = 'none' },
{ type = 'select', label = 'Format', default = format, options = {
@@ -68,7 +68,9 @@ local function closeCreator(cancel)
{ value = 'array', label = 'Array' },
{ value = 'target', label = 'Target'},
}}
- }) or {}
+ })
+
+ if not input then return end
format = input[2]
diff --git a/web/src/features/dev/debug/input.ts b/web/src/features/dev/debug/input.ts
index 35424c387..b7c934165 100644
--- a/web/src/features/dev/debug/input.ts
+++ b/web/src/features/dev/debug/input.ts
@@ -40,6 +40,14 @@ export const debugInput = () => {
max: 10,
icon: 'receipt',
},
+ {
+ type: 'number',
+ label: 'Price',
+ default: 6.5,
+ min: 0,
+ max: 10,
+ icon: 'receipt',
+ },
{
type: 'slider',
label: 'Slide bar',
diff --git a/web/src/features/dialog/components/fields/checkbox.tsx b/web/src/features/dialog/components/fields/checkbox.tsx
index aadeaf230..55a6ca513 100644
--- a/web/src/features/dialog/components/fields/checkbox.tsx
+++ b/web/src/features/dialog/components/fields/checkbox.tsx
@@ -16,6 +16,7 @@ const CheckboxField: React.FC = (props) => {
required={props.row.required}
label={props.row.label}
defaultChecked={props.row.checked}
+ disabled={props.row.disabled}
/>
);
};
diff --git a/web/src/features/dialog/components/fields/date.tsx b/web/src/features/dialog/components/fields/date.tsx
index 1208b291f..ec05f9ce6 100644
--- a/web/src/features/dialog/components/fields/date.tsx
+++ b/web/src/features/dialog/components/fields/date.tsx
@@ -14,7 +14,7 @@ const DateField: React.FC = (props) => {
const controller = useController({
name: `test.${props.index}.value`,
control: props.control,
- rules: { required: props.row.required },
+ rules: { required: props.row.required, min: props.row.min, max: props.row.max },
});
return (
diff --git a/web/src/features/dialog/components/fields/number.tsx b/web/src/features/dialog/components/fields/number.tsx
index 6ea06d29b..3738320bb 100644
--- a/web/src/features/dialog/components/fields/number.tsx
+++ b/web/src/features/dialog/components/fields/number.tsx
@@ -15,7 +15,7 @@ const NumberField: React.FC = (props) => {
name: `test.${props.index}.value`,
control: props.control,
defaultValue: props.row.default,
- rules: { required: props.row.required },
+ rules: { required: props.row.required, min: props.row.min, max: props.row.max },
});
return (
@@ -30,6 +30,7 @@ const NumberField: React.FC = (props) => {
defaultValue={props.row.default}
min={props.row.min}
max={props.row.max}
+ precision={props.row.precision}
disabled={props.row.disabled}
icon={props.row.icon && }
withAsterisk={props.row.required}
diff --git a/web/src/features/skillcheck/indicator.tsx b/web/src/features/skillcheck/indicator.tsx
index 333050e85..674f644fd 100644
--- a/web/src/features/skillcheck/indicator.tsx
+++ b/web/src/features/skillcheck/indicator.tsx
@@ -14,7 +14,7 @@ interface Props {
const Indicator: React.FC = ({ angle, offset, multiplier, handleComplete, skillCheck, className }) => {
const [indicatorAngle, setIndicatorAngle] = useState(-90);
- const [keyPressed, setKeyPressed] = useState(false);
+ const [keyPressed, setKeyPressed] = useState(false);
const interval = useInterval(
() =>
setIndicatorAngle((prevState) => {
@@ -25,8 +25,7 @@ const Indicator: React.FC = ({ angle, offset, multiplier, handleComplete,
const keyHandler = useCallback(
(e: KeyboardEvent) => {
- if (e.key.toLowerCase() !== skillCheck.key.toLowerCase()) return;
- setKeyPressed(true);
+ setKeyPressed(e.key.toLowerCase());
},
[skillCheck]
);
@@ -49,11 +48,13 @@ const Indicator: React.FC = ({ angle, offset, multiplier, handleComplete,
interval.stop();
- setKeyPressed(false);
window.removeEventListener('keydown', keyHandler);
- if (indicatorAngle < angle || indicatorAngle > angle + offset) handleComplete(false);
+ if (keyPressed !== skillCheck.key.toLowerCase() || indicatorAngle < angle || indicatorAngle > angle + offset)
+ handleComplete(false);
else handleComplete(true);
+
+ setKeyPressed(false);
}, [keyPressed]);
return (
diff --git a/web/src/typings/dialog.ts b/web/src/typings/dialog.ts
index 8ab0cd709..b66359f9a 100644
--- a/web/src/typings/dialog.ts
+++ b/web/src/typings/dialog.ts
@@ -40,6 +40,7 @@ export interface ISelect extends BaseField<'select' | 'multi-select', string | s
}
export interface INumber extends BaseField<'number', number> {
+ precision?: number;
min?: number;
max?: number;
}