diff --git a/README.md b/README.md index 02479fcf8..9142ea5b8 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,27 @@ -

Documentation

-
+# 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; }