mirror of
https://github.com/esx-framework/esx_core.git
synced 2026-08-28 21:01:20 +00:00
Merge pull request #1801 from esx-framework/1.14.0
feat(esx_lib): upgrade esx_core to 1.14.0
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
--All client-side functions outsourced from the Core to the lib will be stored here for compatability, e.g:
|
||||
|
||||
ESX.Game.GetClosestEntity = xLib.entity.closest
|
||||
EnumerateEntitiesWithinDistance = xLib.entity.EnumerateWithinDistance
|
||||
ESX.Game.Teleport = xLib.entity.Teleport
|
||||
|
||||
ESX.Streaming = {
|
||||
RequestModel = xLib.streaming.requestModel,
|
||||
RequestStreamedTextureDict = xLib.streaming.requestStreamedTextureDict,
|
||||
RequestNamedPtfxAsset = xLib.streaming.requestNamedPtfxAsset,
|
||||
RequestAnimSet = xLib.streaming.requestAnimSet,
|
||||
RequestAnimDict = xLib.streaming.requestAnimDict,
|
||||
RequestWeaponAsset = xLib.streaming.requestWeaponAsset,
|
||||
}
|
||||
|
||||
ESX.Scaleform = {
|
||||
ShowFreemodeMessage = xLib.scaleform.showFreemodeMessage,
|
||||
ShowBreakingNews = xLib.scaleform.showBreakingNews,
|
||||
ShowPopupWarning = xLib.scaleform.showPopupWarning,
|
||||
ShowTrafficMovie = xLib.scaleform.showTrafficMovie,
|
||||
Utils = {
|
||||
RequestScaleformMovie = xLib.scaleform.utils.requestScaleformMovie,
|
||||
RunMethod = xLib.scaleform.utils.runMethod,
|
||||
},
|
||||
}
|
||||
|
||||
ESX.CreatePointInternal = xLib.points.create
|
||||
ESX.RemovePointInternal = xLib.points.remove
|
||||
ESX.HidePointInternal = xLib.points.hide
|
||||
StartPointsLoop = xLib.points.startLoop
|
||||
ESX.Point = xLib.point
|
||||
|
||||
ESX.RegisterInteraction = xLib.interactions.register
|
||||
ESX.RemoveInteraction = xLib.interactions.remove
|
||||
ESX.GetInteractKey = xLib.interactions.getInteractKey
|
||||
|
||||
ESX.Game.GetPedMugshot = xLib.game.getPedMugshot
|
||||
ESX.Game.SpawnObject = xLib.game.spawnObject
|
||||
ESX.Game.SpawnLocalObject = xLib.game.spawnLocalObject
|
||||
ESX.Game.DeleteVehicle = xLib.game.deleteVehicle
|
||||
ESX.Game.DeleteObject = xLib.game.deleteObject
|
||||
ESX.Game.SpawnVehicle = xLib.game.spawnVehicle
|
||||
ESX.Game.SpawnLocalVehicle = xLib.game.spawnLocalVehicle
|
||||
ESX.Game.IsVehicleEmpty = xLib.game.isVehicleEmpty
|
||||
ESX.Game.GetObjects = xLib.game.getObjects
|
||||
ESX.Game.GetPeds = xLib.game.getPeds
|
||||
ESX.Game.GetVehicles = xLib.game.getVehicles
|
||||
ESX.Game.GetPlayers = xLib.game.getPlayers
|
||||
ESX.Game.GetClosestObject = xLib.game.getClosestObject
|
||||
ESX.Game.GetClosestPed = xLib.game.getClosestPed
|
||||
ESX.Game.GetClosestPlayer = xLib.game.getClosestPlayer
|
||||
ESX.Game.GetClosestVehicle = xLib.game.getClosestVehicle
|
||||
ESX.Game.GetPlayersInArea = xLib.game.getPlayersInArea
|
||||
ESX.Game.GetVehiclesInArea = xLib.game.getVehiclesInArea
|
||||
ESX.Game.IsSpawnPointClear = xLib.game.isSpawnPointClear
|
||||
ESX.Game.GetVehicleInDirection = xLib.game.getVehicleInDirection
|
||||
ESX.Game.GetVehicleProperties = xLib.game.getVehicleProperties
|
||||
ESX.Game.SetVehicleProperties = xLib.game.setVehicleProperties
|
||||
|
||||
function ESX.RegisterInput(command_name, label, input_group, key, on_press, on_release)
|
||||
return xLib.addKeybind({
|
||||
name = command_name,
|
||||
description = label,
|
||||
defaultMapper = input_group,
|
||||
defaultKey = key,
|
||||
onPressed = on_press,
|
||||
onReleased = on_release
|
||||
})
|
||||
end
|
||||
|
||||
---@param raycast table The raycast object returned from ESX.Game.StartRaycasting
|
||||
ESX.Game.StopRaycasting = function(raycast)
|
||||
if raycast and raycast.active then
|
||||
raycast:Stop()
|
||||
end
|
||||
end
|
||||
---@param raycast table The raycast object returned from ESX.Game.StartRaycasting
|
||||
ESX.Game.IsRaycastActive = function(raycast)
|
||||
if raycast and raycast.active then
|
||||
return raycast:IsActive()
|
||||
end
|
||||
return false
|
||||
end
|
||||
---@param raycast table The raycast object returned from ESX.Game.StartRaycasting
|
||||
ESX.Game.GetRaycastResult = function(raycast)
|
||||
if raycast and raycast.active then
|
||||
return raycast.result
|
||||
end
|
||||
return nil
|
||||
end
|
||||
@@ -246,22 +246,6 @@ function ESX.RefreshContext(...)
|
||||
return IsResourceFound('esx_context') and exports['esx_context']:Refresh(...)
|
||||
end
|
||||
|
||||
---@param command_name string The command name
|
||||
---@param label string The label to show
|
||||
---@param input_group string The input group
|
||||
---@param key string The key to bind
|
||||
---@param on_press function The function to call on press
|
||||
---@param on_release? function The function to call on release
|
||||
function ESX.RegisterInput(command_name, label, input_group, key, on_press, on_release)
|
||||
local command = on_release and '+' .. command_name or command_name
|
||||
RegisterCommand(command, on_press, false)
|
||||
Core.Input[command_name] = ESX.HashString(command)
|
||||
if on_release then
|
||||
RegisterCommand('-' .. command_name, on_release, false)
|
||||
end
|
||||
RegisterKeyMapping(command, label or '', input_group or 'keyboard', key or '')
|
||||
end
|
||||
|
||||
---@param menuType string
|
||||
---@param open function The function to call on open
|
||||
---@param close function The function to call on close
|
||||
@@ -459,779 +443,6 @@ function ESX.UI.ShowInventoryItemNotification(add, item, count)
|
||||
})
|
||||
end
|
||||
|
||||
---@param ped integer The ped to get the mugshot of
|
||||
---@param transparent? boolean Whether the mugshot should be transparent
|
||||
function ESX.Game.GetPedMugshot(ped, transparent)
|
||||
if not DoesEntityExist(ped) then
|
||||
return
|
||||
end
|
||||
local mugshot = transparent and RegisterPedheadshotTransparent(ped) or RegisterPedheadshot(ped)
|
||||
|
||||
while not IsPedheadshotReady(mugshot) do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
return mugshot, GetPedheadshotTxdString(mugshot)
|
||||
end
|
||||
|
||||
---@param entity integer The entity to get the coords of
|
||||
---@param coords table | vector3 | vector4 The coords to teleport the entity to
|
||||
---@param cb? function The callback function
|
||||
function ESX.Game.Teleport(entity, coords, cb)
|
||||
|
||||
if DoesEntityExist(entity) then
|
||||
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
|
||||
while not HasCollisionLoadedAroundEntity(entity) do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
SetEntityCoords(entity, coords.x, coords.y, coords.z, false, false, false, false)
|
||||
SetEntityHeading(entity, coords.w or coords.heading or 0.0)
|
||||
end
|
||||
|
||||
if cb then
|
||||
cb()
|
||||
end
|
||||
end
|
||||
|
||||
---@param object integer | string The object to spawn
|
||||
---@param coords table | vector3 The coords to spawn the object at
|
||||
---@param cb? function The callback function
|
||||
---@param networked? boolean Whether the object should be networked
|
||||
---@return integer | nil
|
||||
function ESX.Game.SpawnObject(object, coords, cb, networked)
|
||||
local model = type(object) == "number" and object or joaat(object)
|
||||
|
||||
ESX.Streaming.RequestModel(model)
|
||||
|
||||
local obj = CreateObject(model, coords.x, coords.y, coords.z, networked == nil or networked, false, true)
|
||||
return cb and cb(obj) or obj
|
||||
end
|
||||
|
||||
---@param object integer | string The object to spawn
|
||||
---@param coords table | vector3 The coords to spawn the object at
|
||||
---@param cb? function The callback function
|
||||
---@return nil
|
||||
function ESX.Game.SpawnLocalObject(object, coords, cb)
|
||||
ESX.Game.SpawnObject(object, coords, cb, false)
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to delete
|
||||
---@return nil
|
||||
function ESX.Game.DeleteVehicle(vehicle)
|
||||
SetEntityAsMissionEntity(vehicle, true, true)
|
||||
DeleteVehicle(vehicle)
|
||||
end
|
||||
|
||||
---@param object integer The object to delete
|
||||
---@return nil
|
||||
function ESX.Game.DeleteObject(object)
|
||||
SetEntityAsMissionEntity(object, false, true)
|
||||
DeleteObject(object)
|
||||
end
|
||||
|
||||
---@param vehicleModel integer | string The vehicle to spawn
|
||||
---@param coords table | vector3 The coords to spawn the vehicle at
|
||||
---@param heading number The heading of the vehicle
|
||||
---@param cb? fun(vehicle: number) The callback function
|
||||
---@param networked? boolean Whether the vehicle should be networked
|
||||
---@return number? vehicle
|
||||
function ESX.Game.SpawnVehicle(vehicleModel, coords, heading, cb, networked)
|
||||
if cb and not ESX.IsFunctionReference(cb) then
|
||||
error("Invalid callback function")
|
||||
end
|
||||
|
||||
local model = type(vehicleModel) == "number" and vehicleModel or joaat(vehicleModel)
|
||||
local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z)
|
||||
local isNetworked = networked == nil or networked
|
||||
|
||||
local playerCoords = GetEntityCoords(ESX.PlayerData.ped)
|
||||
if not vector or not playerCoords then
|
||||
return
|
||||
end
|
||||
|
||||
local dist = #(playerCoords - vector)
|
||||
if dist > 424 then -- Onesync infinity Range (https://docs.fivem.net/docs/scripting-reference/onesync/)
|
||||
local executingResource = GetInvokingResource() or "Unknown"
|
||||
return error(("Resource ^5%s^1 Tried to spawn vehicle on the client but the position is too far away (Out of onesync range)."):format(executingResource))
|
||||
end
|
||||
|
||||
local promise = not cb and promise.new()
|
||||
CreateThread(function()
|
||||
local modelHash = ESX.Streaming.RequestModel(model)
|
||||
if not modelHash then
|
||||
if promise then
|
||||
promise:reject(("Tried to spawn invalid vehicle - ^5%s^7!"):format(model))
|
||||
return
|
||||
end
|
||||
error(("Tried to spawn invalid vehicle - ^5%s^7!"):format(model))
|
||||
end
|
||||
|
||||
local vehicle = CreateVehicle(model, vector.x, vector.y, vector.z, heading, isNetworked, true)
|
||||
|
||||
if networked then
|
||||
local id = NetworkGetNetworkIdFromEntity(vehicle)
|
||||
SetNetworkIdCanMigrate(id, true)
|
||||
SetEntityAsMissionEntity(vehicle, true, true)
|
||||
end
|
||||
SetVehicleHasBeenOwnedByPlayer(vehicle, true)
|
||||
SetVehicleNeedsToBeHotwired(vehicle, false)
|
||||
SetModelAsNoLongerNeeded(model)
|
||||
SetVehRadioStation(vehicle, "OFF")
|
||||
|
||||
RequestCollisionAtCoord(vector.x, vector.y, vector.z)
|
||||
while not HasCollisionLoadedAroundEntity(vehicle) do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
if promise then
|
||||
promise:resolve(vehicle)
|
||||
elseif cb then
|
||||
cb(vehicle)
|
||||
end
|
||||
end)
|
||||
|
||||
if promise then
|
||||
return Citizen.Await(promise)
|
||||
end
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to spawn
|
||||
---@param coords table | vector3 The coords to spawn the vehicle at
|
||||
---@param heading number The heading of the vehicle
|
||||
---@param cb? function The callback function
|
||||
---@return nil
|
||||
function ESX.Game.SpawnLocalVehicle(vehicle, coords, heading, cb)
|
||||
ESX.Game.SpawnVehicle(vehicle, coords, heading, cb, false)
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to check
|
||||
---@return boolean
|
||||
function ESX.Game.IsVehicleEmpty(vehicle)
|
||||
return GetVehicleNumberOfPassengers(vehicle) == 0 and IsVehicleSeatFree(vehicle, -1)
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.Game.GetObjects() -- Leave the function for compatibility
|
||||
return GetGamePool("CObject")
|
||||
end
|
||||
|
||||
---@param onlyOtherPeds? boolean Whether to exlude the player ped
|
||||
---@return table
|
||||
function ESX.Game.GetPeds(onlyOtherPeds)
|
||||
local pool = GetGamePool("CPed")
|
||||
|
||||
if onlyOtherPeds then
|
||||
local myPed = ESX.PlayerData.ped
|
||||
for i = 1, #pool do
|
||||
if pool[i] == myPed then
|
||||
table.remove(pool, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return pool
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.Game.GetVehicles() -- Leave the function for compatibility
|
||||
return GetGamePool("CVehicle")
|
||||
end
|
||||
|
||||
---@param onlyOtherPlayers? boolean Whether to exclude the player
|
||||
---@param returnKeyValue? boolean Whether to return the key value pair
|
||||
---@param returnPeds? boolean Whether to return the peds
|
||||
---@return table
|
||||
function ESX.Game.GetPlayers(onlyOtherPlayers, returnKeyValue, returnPeds)
|
||||
local players = {}
|
||||
local active = GetActivePlayers()
|
||||
|
||||
for i = 1, #active do
|
||||
local currentPlayer = active[i]
|
||||
local ped = GetPlayerPed(currentPlayer)
|
||||
|
||||
if DoesEntityExist(ped) and ((onlyOtherPlayers and currentPlayer ~= ESX.playerId) or not onlyOtherPlayers) then
|
||||
if returnKeyValue then
|
||||
players[currentPlayer] = ped
|
||||
else
|
||||
players[#players + 1] = returnPeds and ped or currentPlayer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest object to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestObject(coords, modelFilter)
|
||||
return ESX.Game.GetClosestEntity(ESX.Game.GetObjects(), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest ped to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestPed(coords, modelFilter)
|
||||
return ESX.Game.GetClosestEntity(ESX.Game.GetPeds(true), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest player to
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestPlayer(coords)
|
||||
return ESX.Game.GetClosestEntity(ESX.Game.GetPlayers(true, true), true, coords, nil)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest vehicle to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestVehicle(coords, modelFilter)
|
||||
return ESX.Game.GetClosestEntity(ESX.Game.GetVehicles(), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param entities table The entities to search through
|
||||
---@param isPlayerEntities boolean Whether the entities are players
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return table
|
||||
local function EnumerateEntitiesWithinDistance(entities, isPlayerEntities, coords, maxDistance)
|
||||
local nearbyEntities = {}
|
||||
|
||||
if coords then
|
||||
coords = vector3(coords.x, coords.y, coords.z)
|
||||
else
|
||||
local playerPed = ESX.PlayerData.ped
|
||||
coords = GetEntityCoords(playerPed)
|
||||
end
|
||||
|
||||
for k, entity in pairs(entities) do
|
||||
local distance = #(coords - GetEntityCoords(entity))
|
||||
|
||||
if distance <= maxDistance then
|
||||
nearbyEntities[#nearbyEntities + 1] = isPlayerEntities and k or entity
|
||||
end
|
||||
end
|
||||
|
||||
return nearbyEntities
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return table
|
||||
function ESX.Game.GetPlayersInArea(coords, maxDistance)
|
||||
return EnumerateEntitiesWithinDistance(ESX.Game.GetPlayers(true, true), true, coords, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return table
|
||||
function ESX.Game.GetVehiclesInArea(coords, maxDistance)
|
||||
return EnumerateEntitiesWithinDistance(ESX.Game.GetVehicles(), false, coords, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return boolean
|
||||
function ESX.Game.IsSpawnPointClear(coords, maxDistance)
|
||||
return #ESX.Game.GetVehiclesInArea(coords, maxDistance) == 0
|
||||
end
|
||||
|
||||
---@param shape integer The shape to get the test result from
|
||||
---@return boolean, table, table, integer, integer
|
||||
function ESX.Game.GetShapeTestResultSync(shape)
|
||||
local handle, hit, coords, normal, material, entity
|
||||
repeat
|
||||
handle, hit, coords, normal, material, entity = GetShapeTestResultIncludingMaterial(shape)
|
||||
Wait(0)
|
||||
until handle ~= 1
|
||||
return hit, coords, normal, material, entity
|
||||
end
|
||||
|
||||
---@param depth number The depth to raycast
|
||||
---@vararg any The arguments to pass to the shape test
|
||||
---@return table, boolean, table, table, integer, integer
|
||||
function ESX.Game.RaycastScreen(depth, ...)
|
||||
local world, normal = GetWorldCoordFromScreenCoord(.5, .5)
|
||||
local origin = world + normal
|
||||
local target = world + normal * depth
|
||||
return target, ESX.Game.GetShapeTestResultSync(StartShapeTestLosProbe(origin.x, origin.y, origin.z, target.x, target.y, target.z, ...))
|
||||
end
|
||||
|
||||
---@param entities table The entities to search through
|
||||
---@param isPlayerEntities boolean Whether the entities are players
|
||||
---@param coords? table | vector3 The coords to search from
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestEntity(entities, isPlayerEntities, coords, modelFilter)
|
||||
local closestEntity, closestEntityDistance, filteredEntities = -1, -1, nil
|
||||
|
||||
if coords then
|
||||
coords = vector3(coords.x, coords.y, coords.z)
|
||||
else
|
||||
local playerPed = ESX.PlayerData.ped
|
||||
coords = GetEntityCoords(playerPed)
|
||||
end
|
||||
|
||||
if modelFilter then
|
||||
filteredEntities = {}
|
||||
|
||||
for currentEntityIndex = 1, #entities do
|
||||
if modelFilter[GetEntityModel(entities[currentEntityIndex])] then
|
||||
filteredEntities[#filteredEntities + 1] = entities[currentEntityIndex]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k, entity in pairs(filteredEntities or entities) do
|
||||
local distance = #(coords - GetEntityCoords(entity))
|
||||
|
||||
if closestEntityDistance == -1 or distance < closestEntityDistance then
|
||||
closestEntity, closestEntityDistance = isPlayerEntities and k or entity, distance
|
||||
end
|
||||
end
|
||||
|
||||
return closestEntity, closestEntityDistance
|
||||
end
|
||||
|
||||
---@return integer | nil, vector3 | nil
|
||||
function ESX.Game.GetVehicleInDirection()
|
||||
local _, hit, coords, _, _, entity = ESX.Game.RaycastScreen(5, 10, ESX.PlayerData.ped)
|
||||
if hit and IsEntityAVehicle(entity) then
|
||||
return entity, coords
|
||||
end
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to get the properties of
|
||||
---@return table | nil
|
||||
function ESX.Game.GetVehicleProperties(vehicle)
|
||||
if not DoesEntityExist(vehicle) then
|
||||
return
|
||||
end
|
||||
|
||||
---@type number | number[], number | number[]
|
||||
local colorPrimary, colorSecondary = GetVehicleColours(vehicle)
|
||||
local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle)
|
||||
local dashboardColor = GetVehicleDashboardColor(vehicle)
|
||||
local interiorColor = GetVehicleInteriorColour(vehicle)
|
||||
|
||||
if GetIsVehiclePrimaryColourCustom(vehicle) then
|
||||
colorPrimary = { GetVehicleCustomPrimaryColour(vehicle) }
|
||||
end
|
||||
|
||||
if GetIsVehicleSecondaryColourCustom(vehicle) then
|
||||
colorSecondary = { GetVehicleCustomSecondaryColour(vehicle) }
|
||||
end
|
||||
|
||||
local hasCustomXenonColor, customXenonColorR, customXenonColorG, customXenonColorB = GetVehicleXenonLightsCustomColor(vehicle)
|
||||
local customXenonColor = nil
|
||||
if hasCustomXenonColor then
|
||||
customXenonColor = { customXenonColorR, customXenonColorG, customXenonColorB }
|
||||
end
|
||||
|
||||
local extras = {}
|
||||
for extraId = 0, 20 do
|
||||
if DoesExtraExist(vehicle, extraId) then
|
||||
extras[tostring(extraId)] = IsVehicleExtraTurnedOn(vehicle, extraId)
|
||||
end
|
||||
end
|
||||
|
||||
local doorsBroken, windowsBroken, tyreBurst = {}, {}, {}
|
||||
local numWheels = tostring(GetVehicleNumberOfWheels(vehicle))
|
||||
|
||||
local TyresIndex = { -- Wheel index list according to the number of vehicle wheels.
|
||||
["2"] = { 0, 4 }, -- Bike and cycle.
|
||||
["3"] = { 0, 1, 4, 5 }, -- Vehicle with 3 wheels (get for wheels because some 3 wheels vehicles have 2 wheels on front and one rear or the reverse).
|
||||
["4"] = { 0, 1, 4, 5 }, -- Vehicle with 4 wheels.
|
||||
["6"] = { 0, 1, 2, 3, 4, 5 }, -- Vehicle with 6 wheels.
|
||||
}
|
||||
|
||||
if TyresIndex[numWheels] then
|
||||
for _, idx in pairs(TyresIndex[numWheels]) do
|
||||
tyreBurst[tostring(idx)] = IsVehicleTyreBurst(vehicle, idx, false)
|
||||
end
|
||||
end
|
||||
|
||||
for windowId = 0, 7 do -- 13
|
||||
RollUpWindow(vehicle, windowId) --fix when you put the car away with the window down
|
||||
windowsBroken[tostring(windowId)] = not IsVehicleWindowIntact(vehicle, windowId)
|
||||
end
|
||||
|
||||
local numDoors = GetNumberOfVehicleDoors(vehicle)
|
||||
if numDoors and numDoors > 0 then
|
||||
for doorsId = 0, numDoors do
|
||||
doorsBroken[tostring(doorsId)] = IsVehicleDoorDamaged(vehicle, doorsId)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
model = GetEntityModel(vehicle),
|
||||
doorsBroken = doorsBroken,
|
||||
windowsBroken = windowsBroken,
|
||||
tyreBurst = tyreBurst,
|
||||
tyresCanBurst = GetVehicleTyresCanBurst(vehicle),
|
||||
plate = ESX.Math.Trim(GetVehicleNumberPlateText(vehicle)),
|
||||
plateIndex = GetVehicleNumberPlateTextIndex(vehicle),
|
||||
|
||||
bodyHealth = ESX.Math.Round(GetVehicleBodyHealth(vehicle), 1),
|
||||
engineHealth = ESX.Math.Round(GetVehicleEngineHealth(vehicle), 1),
|
||||
tankHealth = ESX.Math.Round(GetVehiclePetrolTankHealth(vehicle), 1),
|
||||
|
||||
fuelLevel = ESX.Math.Round(GetVehicleFuelLevel(vehicle), 1),
|
||||
dirtLevel = ESX.Math.Round(GetVehicleDirtLevel(vehicle), 1),
|
||||
color1 = colorPrimary,
|
||||
color2 = colorSecondary,
|
||||
|
||||
pearlescentColor = pearlescentColor,
|
||||
wheelColor = wheelColor,
|
||||
|
||||
dashboardColor = dashboardColor,
|
||||
interiorColor = interiorColor,
|
||||
|
||||
wheels = GetVehicleWheelType(vehicle),
|
||||
windowTint = GetVehicleWindowTint(vehicle),
|
||||
xenonColor = GetVehicleXenonLightsColor(vehicle),
|
||||
customXenonColor = customXenonColor,
|
||||
|
||||
neonEnabled = { IsVehicleNeonLightEnabled(vehicle, 0), IsVehicleNeonLightEnabled(vehicle, 1), IsVehicleNeonLightEnabled(vehicle, 2), IsVehicleNeonLightEnabled(vehicle, 3) },
|
||||
|
||||
neonColor = table.pack(GetVehicleNeonLightsColour(vehicle)),
|
||||
extras = extras,
|
||||
tyreSmokeColor = table.pack(GetVehicleTyreSmokeColor(vehicle)),
|
||||
|
||||
modSpoilers = GetVehicleMod(vehicle, 0),
|
||||
modFrontBumper = GetVehicleMod(vehicle, 1),
|
||||
modRearBumper = GetVehicleMod(vehicle, 2),
|
||||
modSideSkirt = GetVehicleMod(vehicle, 3),
|
||||
modExhaust = GetVehicleMod(vehicle, 4),
|
||||
modFrame = GetVehicleMod(vehicle, 5),
|
||||
modGrille = GetVehicleMod(vehicle, 6),
|
||||
modHood = GetVehicleMod(vehicle, 7),
|
||||
modFender = GetVehicleMod(vehicle, 8),
|
||||
modRightFender = GetVehicleMod(vehicle, 9),
|
||||
modRoof = GetVehicleMod(vehicle, 10),
|
||||
modRoofLivery = GetVehicleRoofLivery(vehicle),
|
||||
|
||||
modEngine = GetVehicleMod(vehicle, 11),
|
||||
modBrakes = GetVehicleMod(vehicle, 12),
|
||||
modTransmission = GetVehicleMod(vehicle, 13),
|
||||
modHorns = GetVehicleMod(vehicle, 14),
|
||||
modSuspension = GetVehicleMod(vehicle, 15),
|
||||
modArmor = GetVehicleMod(vehicle, 16),
|
||||
|
||||
modTurbo = IsToggleModOn(vehicle, 18),
|
||||
modSmokeEnabled = IsToggleModOn(vehicle, 20),
|
||||
modXenon = IsToggleModOn(vehicle, 22),
|
||||
|
||||
modFrontWheels = GetVehicleMod(vehicle, 23),
|
||||
modCustomFrontWheels = GetVehicleModVariation(vehicle, 23),
|
||||
modBackWheels = GetVehicleMod(vehicle, 24),
|
||||
modCustomBackWheels = GetVehicleModVariation(vehicle, 24),
|
||||
|
||||
modPlateHolder = GetVehicleMod(vehicle, 25),
|
||||
modVanityPlate = GetVehicleMod(vehicle, 26),
|
||||
modTrimA = GetVehicleMod(vehicle, 27),
|
||||
modOrnaments = GetVehicleMod(vehicle, 28),
|
||||
modDashboard = GetVehicleMod(vehicle, 29),
|
||||
modDial = GetVehicleMod(vehicle, 30),
|
||||
modDoorSpeaker = GetVehicleMod(vehicle, 31),
|
||||
modSeats = GetVehicleMod(vehicle, 32),
|
||||
modSteeringWheel = GetVehicleMod(vehicle, 33),
|
||||
modShifterLeavers = GetVehicleMod(vehicle, 34),
|
||||
modAPlate = GetVehicleMod(vehicle, 35),
|
||||
modSpeakers = GetVehicleMod(vehicle, 36),
|
||||
modTrunk = GetVehicleMod(vehicle, 37),
|
||||
modHydrolic = GetVehicleMod(vehicle, 38),
|
||||
modEngineBlock = GetVehicleMod(vehicle, 39),
|
||||
modAirFilter = GetVehicleMod(vehicle, 40),
|
||||
modStruts = GetVehicleMod(vehicle, 41),
|
||||
modArchCover = GetVehicleMod(vehicle, 42),
|
||||
modAerials = GetVehicleMod(vehicle, 43),
|
||||
modTrimB = GetVehicleMod(vehicle, 44),
|
||||
modTank = GetVehicleMod(vehicle, 45),
|
||||
modWindows = GetVehicleMod(vehicle, 46),
|
||||
modLivery = GetVehicleMod(vehicle, 48) == -1 and GetVehicleLivery(vehicle) or GetVehicleMod(vehicle, 48),
|
||||
modLightbar = GetVehicleMod(vehicle, 49),
|
||||
}
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to set the properties of
|
||||
---@param props table The properties to set
|
||||
---@return nil
|
||||
function ESX.Game.SetVehicleProperties(vehicle, props)
|
||||
if not DoesEntityExist(vehicle) then
|
||||
return
|
||||
end
|
||||
local colorPrimary, colorSecondary = GetVehicleColours(vehicle)
|
||||
local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle)
|
||||
SetVehicleModKit(vehicle, 0)
|
||||
|
||||
if props.tyresCanBurst ~= nil then
|
||||
SetVehicleTyresCanBurst(vehicle, props.tyresCanBurst)
|
||||
end
|
||||
|
||||
if props.plate ~= nil then
|
||||
SetVehicleNumberPlateText(vehicle, props.plate)
|
||||
end
|
||||
if props.plateIndex ~= nil then
|
||||
SetVehicleNumberPlateTextIndex(vehicle, props.plateIndex)
|
||||
end
|
||||
if props.bodyHealth ~= nil then
|
||||
SetVehicleBodyHealth(vehicle, props.bodyHealth + 0.0)
|
||||
end
|
||||
if props.engineHealth ~= nil then
|
||||
SetVehicleEngineHealth(vehicle, props.engineHealth + 0.0)
|
||||
end
|
||||
if props.tankHealth ~= nil then
|
||||
SetVehiclePetrolTankHealth(vehicle, props.tankHealth + 0.0)
|
||||
end
|
||||
if props.fuelLevel ~= nil then
|
||||
SetVehicleFuelLevel(vehicle, props.fuelLevel + 0.0)
|
||||
end
|
||||
if props.dirtLevel ~= nil then
|
||||
SetVehicleDirtLevel(vehicle, props.dirtLevel + 0.0)
|
||||
end
|
||||
if props.color1 ~= nil then
|
||||
if type(props.color1) == "table" then
|
||||
SetVehicleCustomPrimaryColour(vehicle, props.color1[1], props.color1[2], props.color1[3])
|
||||
else
|
||||
SetVehicleColours(vehicle, props.color1, colorSecondary)
|
||||
end
|
||||
end
|
||||
if props.color2 ~= nil then
|
||||
if type(props.color2) == "table" then
|
||||
SetVehicleCustomSecondaryColour(vehicle, props.color2[1], props.color2[2], props.color2[3])
|
||||
else
|
||||
SetVehicleColours(vehicle, props.color1 or colorPrimary, props.color2)
|
||||
end
|
||||
end
|
||||
if props.pearlescentColor ~= nil then
|
||||
SetVehicleExtraColours(vehicle, props.pearlescentColor, wheelColor)
|
||||
end
|
||||
|
||||
if props.interiorColor ~= nil then
|
||||
SetVehicleInteriorColor(vehicle, props.interiorColor)
|
||||
end
|
||||
|
||||
if props.dashboardColor ~= nil then
|
||||
SetVehicleDashboardColor(vehicle, props.dashboardColor)
|
||||
end
|
||||
|
||||
if props.wheelColor ~= nil then
|
||||
SetVehicleExtraColours(vehicle, props.pearlescentColor or pearlescentColor, props.wheelColor)
|
||||
end
|
||||
if props.wheels ~= nil then
|
||||
SetVehicleWheelType(vehicle, props.wheels)
|
||||
end
|
||||
if props.windowTint ~= nil then
|
||||
SetVehicleWindowTint(vehicle, props.windowTint)
|
||||
end
|
||||
|
||||
if props.neonEnabled ~= nil then
|
||||
SetVehicleNeonLightEnabled(vehicle, 0, props.neonEnabled[1])
|
||||
SetVehicleNeonLightEnabled(vehicle, 1, props.neonEnabled[2])
|
||||
SetVehicleNeonLightEnabled(vehicle, 2, props.neonEnabled[3])
|
||||
SetVehicleNeonLightEnabled(vehicle, 3, props.neonEnabled[4])
|
||||
end
|
||||
|
||||
if props.extras ~= nil then
|
||||
for extraId, enabled in pairs(props.extras) do
|
||||
extraId = tonumber(extraId)
|
||||
if extraId then
|
||||
SetVehicleExtra(vehicle, extraId, not enabled)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if props.neonColor ~= nil then
|
||||
SetVehicleNeonLightsColour(vehicle, props.neonColor[1], props.neonColor[2], props.neonColor[3])
|
||||
end
|
||||
if props.xenonColor ~= nil then
|
||||
SetVehicleXenonLightsColor(vehicle, props.xenonColor)
|
||||
end
|
||||
if props.customXenonColor ~= nil then
|
||||
SetVehicleXenonLightsCustomColor(vehicle, props.customXenonColor[1], props.customXenonColor[2], props.customXenonColor[3])
|
||||
end
|
||||
if props.modSmokeEnabled ~= nil then
|
||||
ToggleVehicleMod(vehicle, 20, true)
|
||||
end
|
||||
if props.tyreSmokeColor ~= nil then
|
||||
SetVehicleTyreSmokeColor(vehicle, props.tyreSmokeColor[1], props.tyreSmokeColor[2], props.tyreSmokeColor[3])
|
||||
end
|
||||
if props.modSpoilers ~= nil then
|
||||
SetVehicleMod(vehicle, 0, props.modSpoilers, false)
|
||||
end
|
||||
if props.modFrontBumper ~= nil then
|
||||
SetVehicleMod(vehicle, 1, props.modFrontBumper, false)
|
||||
end
|
||||
if props.modRearBumper ~= nil then
|
||||
SetVehicleMod(vehicle, 2, props.modRearBumper, false)
|
||||
end
|
||||
if props.modSideSkirt ~= nil then
|
||||
SetVehicleMod(vehicle, 3, props.modSideSkirt, false)
|
||||
end
|
||||
if props.modExhaust ~= nil then
|
||||
SetVehicleMod(vehicle, 4, props.modExhaust, false)
|
||||
end
|
||||
if props.modFrame ~= nil then
|
||||
SetVehicleMod(vehicle, 5, props.modFrame, false)
|
||||
end
|
||||
if props.modGrille ~= nil then
|
||||
SetVehicleMod(vehicle, 6, props.modGrille, false)
|
||||
end
|
||||
if props.modHood ~= nil then
|
||||
SetVehicleMod(vehicle, 7, props.modHood, false)
|
||||
end
|
||||
if props.modFender ~= nil then
|
||||
SetVehicleMod(vehicle, 8, props.modFender, false)
|
||||
end
|
||||
if props.modRightFender ~= nil then
|
||||
SetVehicleMod(vehicle, 9, props.modRightFender, false)
|
||||
end
|
||||
if props.modRoof ~= nil then
|
||||
SetVehicleMod(vehicle, 10, props.modRoof, false)
|
||||
end
|
||||
|
||||
if props.modRoofLivery ~= nil then
|
||||
SetVehicleRoofLivery(vehicle, props.modRoofLivery)
|
||||
end
|
||||
|
||||
if props.modEngine ~= nil then
|
||||
SetVehicleMod(vehicle, 11, props.modEngine, false)
|
||||
end
|
||||
if props.modBrakes ~= nil then
|
||||
SetVehicleMod(vehicle, 12, props.modBrakes, false)
|
||||
end
|
||||
if props.modTransmission ~= nil then
|
||||
SetVehicleMod(vehicle, 13, props.modTransmission, false)
|
||||
end
|
||||
if props.modHorns ~= nil then
|
||||
SetVehicleMod(vehicle, 14, props.modHorns, false)
|
||||
end
|
||||
if props.modSuspension ~= nil then
|
||||
SetVehicleMod(vehicle, 15, props.modSuspension, false)
|
||||
end
|
||||
if props.modArmor ~= nil then
|
||||
SetVehicleMod(vehicle, 16, props.modArmor, false)
|
||||
end
|
||||
if props.modTurbo ~= nil then
|
||||
ToggleVehicleMod(vehicle, 18, props.modTurbo)
|
||||
end
|
||||
if props.modXenon ~= nil then
|
||||
ToggleVehicleMod(vehicle, 22, props.modXenon)
|
||||
end
|
||||
if props.modFrontWheels ~= nil then
|
||||
SetVehicleMod(vehicle, 23, props.modFrontWheels, props.modCustomFrontWheels)
|
||||
end
|
||||
if props.modBackWheels ~= nil then
|
||||
SetVehicleMod(vehicle, 24, props.modBackWheels, props.modCustomBackWheels)
|
||||
end
|
||||
if props.modPlateHolder ~= nil then
|
||||
SetVehicleMod(vehicle, 25, props.modPlateHolder, false)
|
||||
end
|
||||
if props.modVanityPlate ~= nil then
|
||||
SetVehicleMod(vehicle, 26, props.modVanityPlate, false)
|
||||
end
|
||||
if props.modTrimA ~= nil then
|
||||
SetVehicleMod(vehicle, 27, props.modTrimA, false)
|
||||
end
|
||||
if props.modOrnaments ~= nil then
|
||||
SetVehicleMod(vehicle, 28, props.modOrnaments, false)
|
||||
end
|
||||
if props.modDashboard ~= nil then
|
||||
SetVehicleMod(vehicle, 29, props.modDashboard, false)
|
||||
end
|
||||
if props.modDial ~= nil then
|
||||
SetVehicleMod(vehicle, 30, props.modDial, false)
|
||||
end
|
||||
if props.modDoorSpeaker ~= nil then
|
||||
SetVehicleMod(vehicle, 31, props.modDoorSpeaker, false)
|
||||
end
|
||||
if props.modSeats ~= nil then
|
||||
SetVehicleMod(vehicle, 32, props.modSeats, false)
|
||||
end
|
||||
if props.modSteeringWheel ~= nil then
|
||||
SetVehicleMod(vehicle, 33, props.modSteeringWheel, false)
|
||||
end
|
||||
if props.modShifterLeavers ~= nil then
|
||||
SetVehicleMod(vehicle, 34, props.modShifterLeavers, false)
|
||||
end
|
||||
if props.modAPlate ~= nil then
|
||||
SetVehicleMod(vehicle, 35, props.modAPlate, false)
|
||||
end
|
||||
if props.modSpeakers ~= nil then
|
||||
SetVehicleMod(vehicle, 36, props.modSpeakers, false)
|
||||
end
|
||||
if props.modTrunk ~= nil then
|
||||
SetVehicleMod(vehicle, 37, props.modTrunk, false)
|
||||
end
|
||||
if props.modHydrolic ~= nil then
|
||||
SetVehicleMod(vehicle, 38, props.modHydrolic, false)
|
||||
end
|
||||
if props.modEngineBlock ~= nil then
|
||||
SetVehicleMod(vehicle, 39, props.modEngineBlock, false)
|
||||
end
|
||||
if props.modAirFilter ~= nil then
|
||||
SetVehicleMod(vehicle, 40, props.modAirFilter, false)
|
||||
end
|
||||
if props.modStruts ~= nil then
|
||||
SetVehicleMod(vehicle, 41, props.modStruts, false)
|
||||
end
|
||||
if props.modArchCover ~= nil then
|
||||
SetVehicleMod(vehicle, 42, props.modArchCover, false)
|
||||
end
|
||||
if props.modAerials ~= nil then
|
||||
SetVehicleMod(vehicle, 43, props.modAerials, false)
|
||||
end
|
||||
if props.modTrimB ~= nil then
|
||||
SetVehicleMod(vehicle, 44, props.modTrimB, false)
|
||||
end
|
||||
if props.modTank ~= nil then
|
||||
SetVehicleMod(vehicle, 45, props.modTank, false)
|
||||
end
|
||||
if props.modWindows ~= nil then
|
||||
SetVehicleMod(vehicle, 46, props.modWindows, false)
|
||||
end
|
||||
|
||||
if props.modLivery ~= nil then
|
||||
SetVehicleMod(vehicle, 48, props.modLivery, false)
|
||||
SetVehicleLivery(vehicle, props.modLivery)
|
||||
end
|
||||
|
||||
if props.windowsBroken ~= nil then
|
||||
for k, v in pairs(props.windowsBroken) do
|
||||
if v then
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
RemoveVehicleWindow(vehicle, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if props.doorsBroken ~= nil then
|
||||
for k, v in pairs(props.doorsBroken) do
|
||||
if v then
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
SetVehicleDoorBroken(vehicle, k, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if props.tyreBurst ~= nil then
|
||||
for k, v in pairs(props.tyreBurst) do
|
||||
if v then
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
SetVehicleTyreBurst(vehicle, k, true, 1000.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param coords vector3 | table coords to get the closest pickup to
|
||||
---@param text string The text to display
|
||||
---@param size? number The size of the text
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
local Point = ESX.Class()
|
||||
|
||||
local nearby, loop = {}, nil
|
||||
|
||||
function Point:constructor(properties)
|
||||
self.coords = properties.coords
|
||||
self.hidden = properties.hidden
|
||||
self.enter = properties.enter
|
||||
self.leave = properties.leave
|
||||
self.inside = properties.inside
|
||||
self.handle = ESX.CreatePointInternal(properties.coords, properties.distance, properties.hidden, function()
|
||||
nearby[self.handle] = self
|
||||
if self.enter then
|
||||
self:enter()
|
||||
end
|
||||
if not loop then
|
||||
loop = true
|
||||
CreateThread(function()
|
||||
while loop do
|
||||
local coords = GetEntityCoords(ESX.PlayerData.ped)
|
||||
for _, point in pairs(nearby) do
|
||||
if point.inside then
|
||||
point:inside(#(coords - point.coords))
|
||||
end
|
||||
end
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end, function()
|
||||
nearby[self.handle] = nil
|
||||
if self.leave then
|
||||
self:leave()
|
||||
end
|
||||
if #nearby == 0 then
|
||||
loop = false
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Point:delete()
|
||||
ESX.RemovePointInternal(self.handle)
|
||||
end
|
||||
|
||||
function Point:toggle(hidden)
|
||||
if hidden == nil then
|
||||
hidden = not self.hidden
|
||||
end
|
||||
self.hidden = hidden
|
||||
ESX.HidePointInternal(self.handle, hidden)
|
||||
end
|
||||
|
||||
return Point
|
||||
@@ -1,10 +1,14 @@
|
||||
-- ESX glue over the generic xLib.cache.
|
||||
-- ped and weapon are tracked by the lib cache; this module mirrors them into
|
||||
-- ESX.PlayerData, re-emits the legacy esx: events resources depend on, and keeps
|
||||
-- the vehicle enter/exit state machine that produces the richer vehicle events.
|
||||
|
||||
Actions = {}
|
||||
Actions._index = Actions
|
||||
|
||||
Actions.inVehicle = false
|
||||
Actions.enteringVehicle = false
|
||||
Actions.inPauseMenu = false
|
||||
Actions.currentWeapon = false
|
||||
|
||||
function Actions:GetSeatPedIsIn()
|
||||
for i = -1, 16 do
|
||||
@@ -55,21 +59,6 @@ function Actions:TrackPedCoordsOnce()
|
||||
end)
|
||||
end
|
||||
|
||||
function Actions:TrackPed()
|
||||
local playerPed = ESX.PlayerData.ped
|
||||
local newPed = PlayerPedId()
|
||||
|
||||
if playerPed ~= newPed then
|
||||
ESX.SetPlayerData("ped", newPed)
|
||||
|
||||
TriggerEvent("esx:playerPedChanged", newPed)
|
||||
|
||||
if Config.EnableDebug then
|
||||
print("[DEBUG] Player ped changed:", newPed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Actions:TrackPauseMenu()
|
||||
local isActive = IsPauseMenuActive()
|
||||
|
||||
@@ -189,46 +178,43 @@ function Actions:TrackSeat()
|
||||
end
|
||||
end
|
||||
|
||||
function Actions:TrackWeapon()
|
||||
---@type number|false
|
||||
local newWeapon = GetSelectedPedWeapon(ESX.PlayerData.ped)
|
||||
newWeapon = newWeapon ~= `WEAPON_UNARMED` and newWeapon or false
|
||||
|
||||
if newWeapon ~= self.currentWeapon then
|
||||
self.currentWeapon = newWeapon
|
||||
ESX.SetPlayerData("weapon", self.currentWeapon)
|
||||
TriggerEvent("esx:weaponChanged", self.currentWeapon)
|
||||
|
||||
if Config.EnableDebug then
|
||||
print("[DEBUG] Weapon changed:", self.currentWeapon)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Actions:SlowLoop()
|
||||
CreateThread(function()
|
||||
while ESX.PlayerLoaded do
|
||||
self:TrackPauseMenu()
|
||||
self:TrackVehicle()
|
||||
self:TrackWeapon()
|
||||
Wait(500)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Actions:PedLoop()
|
||||
CreateThread(function()
|
||||
while ESX.PlayerLoaded do
|
||||
self:TrackPed()
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Actions:Init()
|
||||
-- Re-seed the cached values on every (re)login. The change handlers below are
|
||||
-- registered once at file load so a relogin never stacks duplicate handlers.
|
||||
ESX.SetPlayerData("ped", xLib.cache.ped)
|
||||
ESX.SetPlayerData("weapon", xLib.cache.weapon)
|
||||
|
||||
self:SlowLoop()
|
||||
self:PedLoop()
|
||||
self:TrackPedCoordsOnce()
|
||||
end
|
||||
|
||||
-- Mirror the lib cache into ESX.PlayerData and re-emit the legacy esx: events.
|
||||
AddEventHandler("xLib:cache:ped", function(ped)
|
||||
ESX.SetPlayerData("ped", ped)
|
||||
TriggerEvent("esx:playerPedChanged", ped)
|
||||
|
||||
if Config.EnableDebug then
|
||||
print("[DEBUG] Player ped changed:", ped)
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("xLib:cache:weapon", function(weapon)
|
||||
ESX.SetPlayerData("weapon", weapon)
|
||||
TriggerEvent("esx:weaponChanged", weapon)
|
||||
|
||||
if Config.EnableDebug then
|
||||
print("[DEBUG] Weapon changed:", weapon)
|
||||
end
|
||||
end)
|
||||
|
||||
Actions:Init()
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
local interactions = {}
|
||||
local pressedInteractions = {}
|
||||
|
||||
function ESX.RemoveInteraction(name)
|
||||
if not interactions[name] then return end
|
||||
interactions[name] = nil
|
||||
end
|
||||
|
||||
ESX.RegisterInteraction = function(name, onPress, condition)
|
||||
interactions[name] = {
|
||||
condition = condition or function() return true end,
|
||||
onPress = onPress,
|
||||
creator = GetInvokingResource() or "es_extended"
|
||||
}
|
||||
end
|
||||
|
||||
function ESX.GetInteractKey()
|
||||
local hash = joaat('esx_interact') | 0x80000000
|
||||
return GetControlInstructionalButton(0, hash, true):sub(3)
|
||||
end
|
||||
|
||||
ESX.RegisterInput("esx_interact", "Interact", "keyboard", "e", function()
|
||||
for _, interaction in pairs(interactions) do
|
||||
local success, result = pcall(interaction.condition)
|
||||
if success and result then
|
||||
pressedInteractions[#pressedInteractions+1] = interaction
|
||||
interaction.onPress()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStop", function(resource)
|
||||
for name, interaction in pairs(interactions) do
|
||||
if interaction.creator == resource then
|
||||
interactions[name] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -1,54 +0,0 @@
|
||||
local points = {}
|
||||
|
||||
function ESX.CreatePointInternal(coords, distance, hidden, enter, leave)
|
||||
local point = {
|
||||
coords = coords,
|
||||
distance = distance,
|
||||
hidden = hidden,
|
||||
enter = enter,
|
||||
leave = leave,
|
||||
resource = GetInvokingResource()
|
||||
}
|
||||
local handle = ESX.Table.SizeOf(points) + 1
|
||||
points[handle] = point
|
||||
return handle
|
||||
end
|
||||
|
||||
function ESX.RemovePointInternal(handle)
|
||||
points[handle] = nil
|
||||
end
|
||||
|
||||
function ESX.HidePointInternal(handle, hidden)
|
||||
if points[handle] then
|
||||
points[handle].hidden = hidden
|
||||
end
|
||||
end
|
||||
|
||||
function StartPointsLoop()
|
||||
CreateThread(function()
|
||||
while true do
|
||||
local coords = GetEntityCoords(ESX.PlayerData.ped)
|
||||
for handle, point in pairs(points) do
|
||||
if not point.hidden and #(coords - point.coords) <= point.distance then
|
||||
if not point.nearby then
|
||||
points[handle].nearby = true
|
||||
points[handle].enter()
|
||||
end
|
||||
elseif point.nearby then
|
||||
points[handle].nearby = false
|
||||
points[handle].leave()
|
||||
end
|
||||
end
|
||||
Wait(500)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
AddEventHandler('onResourceStop', function(resource)
|
||||
for handle, point in pairs(points) do
|
||||
if point.resource == resource then
|
||||
points[handle] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -6,6 +6,7 @@ lua54 'yes'
|
||||
version '1.13.5'
|
||||
|
||||
shared_scripts {
|
||||
'@esx_lib/imports.lua',
|
||||
'locale.lua',
|
||||
|
||||
'shared/config/main.lua',
|
||||
@@ -15,6 +16,7 @@ shared_scripts {
|
||||
'shared/main.lua',
|
||||
'shared/functions.lua',
|
||||
'shared/modules/*.lua',
|
||||
'shared/compat.lua'
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
@@ -39,24 +41,23 @@ server_scripts {
|
||||
'server/modules/createJob.lua',
|
||||
'server/migration/**/main.lua',
|
||||
'server/migration/main.lua',
|
||||
|
||||
'server/compat.lua'
|
||||
}
|
||||
|
||||
client_scripts {
|
||||
'client/main.lua',
|
||||
'client/functions.lua',
|
||||
'client/compat.lua',
|
||||
'client/modules/wrapper.lua',
|
||||
'client/modules/callback.lua',
|
||||
'client/modules/adjustments.lua',
|
||||
'client/modules/points.lua',
|
||||
|
||||
'client/modules/events.lua',
|
||||
|
||||
'client/modules/actions.lua',
|
||||
'client/modules/death.lua',
|
||||
'client/modules/npwd.lua',
|
||||
'client/modules/interactions.lua',
|
||||
'client/modules/scaleform.lua',
|
||||
'client/modules/streaming.lua',
|
||||
}
|
||||
|
||||
ui_page {
|
||||
|
||||
@@ -87,7 +87,7 @@ if not IsDuplicityVersion() then -- Only register this event for the client
|
||||
end)
|
||||
end
|
||||
|
||||
local external = { { "Class", "class.lua" }, { "Point", "point.lua" } }
|
||||
local external = { { "Class", "class.lua" } }
|
||||
for i = 1, #external do
|
||||
local module = external[i]
|
||||
local path = string.format("client/imports/%s", module[2])
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
--All server-side functions outsourced from the Core to the lib will be stored here for compatability, e.g:
|
||||
|
||||
ESX.OneSync.GetPlayersInArea = xLib.onesync.getPlayersInArea
|
||||
ESX.OneSync.GetClosestPlayer = xLib.onesync.getClosestPlayer
|
||||
ESX.OneSync.GetPedsInArea = xLib.onesync.getPedsInArea
|
||||
ESX.OneSync.GetObjectsInArea = xLib.onesync.getObjectsInArea
|
||||
ESX.OneSync.GetVehiclesInArea = xLib.onesync.getVehiclesInArea
|
||||
ESX.OneSync.GetClosestPed = xLib.onesync.getClosestPed
|
||||
ESX.OneSync.GetClosestObject = xLib.onesync.getClosestObject
|
||||
ESX.OneSync.GetClosestVehicle = xLib.onesync.getClosestVehicle
|
||||
@@ -1,84 +1,5 @@
|
||||
ESX.OneSync = {}
|
||||
|
||||
---@param source number|vector3
|
||||
---@param closest boolean
|
||||
---@param distance? number
|
||||
---@param ignore? table
|
||||
---@param routingBucket? number
|
||||
local function getNearbyPlayers(source, closest, distance, ignore, routingBucket)
|
||||
local result = {}
|
||||
local count = 0
|
||||
local playerPed
|
||||
local playerCoords
|
||||
ignore = ignore or {}
|
||||
|
||||
if not distance then
|
||||
distance = 100
|
||||
end
|
||||
|
||||
if type(source) == "number" then
|
||||
playerPed = GetPlayerPed(source)
|
||||
|
||||
if not source then
|
||||
error("Received invalid first argument (source); should be playerId")
|
||||
end
|
||||
|
||||
playerCoords = GetEntityCoords(playerPed)
|
||||
|
||||
if not playerCoords then
|
||||
error("Received nil value (playerCoords); perhaps source is nil at first place?")
|
||||
end
|
||||
end
|
||||
|
||||
if type(source) == "vector3" then
|
||||
playerCoords = source
|
||||
|
||||
if not playerCoords then
|
||||
error("Received nil value (playerCoords); perhaps source is nil at first place?")
|
||||
end
|
||||
end
|
||||
|
||||
for _, xPlayer in pairs(ESX.Players) do
|
||||
if not ignore[xPlayer.source] and (not routingBucket or GetPlayerRoutingBucket(xPlayer.source) == routingBucket) then
|
||||
local entity = GetPlayerPed(xPlayer.source)
|
||||
local coords = GetEntityCoords(entity)
|
||||
|
||||
if not closest then
|
||||
local dist = #(playerCoords - coords)
|
||||
if dist <= distance then
|
||||
count = count + 1
|
||||
result[count] = { id = xPlayer.source, ped = NetworkGetNetworkIdFromEntity(entity), coords = coords, dist = dist }
|
||||
end
|
||||
else
|
||||
if xPlayer.source ~= source then
|
||||
local dist = #(playerCoords - coords)
|
||||
if dist <= (result.dist or distance) then
|
||||
result = { id = xPlayer.source, ped = NetworkGetNetworkIdFromEntity(entity), coords = coords, dist = dist }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---@param source vector3|number playerId or vector3 coordinates
|
||||
---@param maxDistance number
|
||||
---@param ignore? table playerIds to ignore, where the key is playerId and value is true
|
||||
---@param routingBucket? number
|
||||
function ESX.OneSync.GetPlayersInArea(source, maxDistance, ignore, routingBucket)
|
||||
return getNearbyPlayers(source, false, maxDistance, ignore, routingBucket)
|
||||
end
|
||||
|
||||
---@param source vector3|number playerId or vector3 coordinates
|
||||
---@param maxDistance number
|
||||
---@param ignore? table playerIds to ignore, where the key is playerId and value is true
|
||||
---@param routingBucket? number
|
||||
function ESX.OneSync.GetClosestPlayer(source, maxDistance, ignore, routingBucket)
|
||||
return getNearbyPlayers(source, true, maxDistance, ignore, routingBucket)
|
||||
end
|
||||
|
||||
---@param vehicleModel number|string
|
||||
---@param coords vector3|table
|
||||
---@param heading number
|
||||
@@ -304,84 +225,3 @@ function ESX.OneSync.SpawnPedInVehicle(model, vehicle, seat, cb)
|
||||
return Citizen.Await(promise)
|
||||
end
|
||||
end
|
||||
|
||||
local function getNearbyEntities(entities, coords, modelFilter, maxDistance, isPed)
|
||||
local nearbyEntities = {}
|
||||
coords = type(coords) == "number" and GetEntityCoords(GetPlayerPed(coords)) or vector3(coords.x, coords.y, coords.z)
|
||||
for _, entity in pairs(entities) do
|
||||
if not isPed or (isPed and not IsPedAPlayer(entity)) then
|
||||
if not modelFilter or modelFilter[GetEntityModel(entity)] then
|
||||
local entityCoords = GetEntityCoords(entity)
|
||||
if not maxDistance or #(coords - entityCoords) <= maxDistance then
|
||||
nearbyEntities[#nearbyEntities + 1] = NetworkGetNetworkIdFromEntity(entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nearbyEntities
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function ESX.OneSync.GetPedsInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllPeds(), coords, modelFilter, maxDistance, true)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function ESX.OneSync.GetObjectsInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllObjects(), coords, modelFilter, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table | nil models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function ESX.OneSync.GetVehiclesInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllVehicles(), coords, modelFilter, maxDistance)
|
||||
end
|
||||
|
||||
local function getClosestEntity(entities, coords, modelFilter, isPed)
|
||||
local distance, closestEntity, closestCoords = 100, 0, vector3(0, 0, 0)
|
||||
coords = type(coords) == "number" and GetEntityCoords(GetPlayerPed(coords)) or vector3(coords.x, coords.y, coords.z)
|
||||
|
||||
for _, entity in pairs(entities) do
|
||||
if not isPed or (isPed and not IsPedAPlayer(entity)) then
|
||||
if not modelFilter or modelFilter[GetEntityModel(entity)] then
|
||||
local entityCoords = GetEntityCoords(entity)
|
||||
local dist = #(coords - entityCoords)
|
||||
if dist < distance then
|
||||
closestEntity, distance, closestCoords = entity, dist, entityCoords
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return NetworkGetNetworkIdFromEntity(closestEntity), distance, closestCoords
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function ESX.OneSync.GetClosestPed(coords, modelFilter)
|
||||
return getClosestEntity(GetAllPeds(), coords, modelFilter, true)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function ESX.OneSync.GetClosestObject(coords, modelFilter)
|
||||
return getClosestEntity(GetAllObjects(), coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function ESX.OneSync.GetClosestVehicle(coords, modelFilter)
|
||||
return getClosestEntity(GetAllVehicles(), coords, modelFilter)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
--All shared functions outsourced from the Core to the lib will be stored here for compatability, e.g:
|
||||
|
||||
ESX.Table = {}
|
||||
|
||||
ESX.SetTimeout = xLib.timeout.setTimeout
|
||||
ESX.ClearTimeout = xLib.timeout.clearTimeout
|
||||
ESX.Await = xLib.waitFor
|
||||
ESX.Table.SizeOf = xLib.table.sizeOf
|
||||
ESX.Table.Set = xLib.table.set
|
||||
ESX.Table.IndexOf = xLib.table.indexOf
|
||||
ESX.Table.LastIndexOf = xLib.table.lastIndexOf
|
||||
ESX.Table.Find = xLib.table.find
|
||||
ESX.Table.FindIndex = xLib.table.findIndex
|
||||
ESX.Table.Filter = xLib.table.filter
|
||||
ESX.Table.Map = xLib.table.map
|
||||
ESX.Table.Reverse = xLib.table.reverse
|
||||
ESX.Table.Clone = xLib.table.clone
|
||||
ESX.Table.Concat = xLib.table.concat
|
||||
ESX.Table.Join = xLib.table.join
|
||||
ESX.Table.TableContains = xLib.table.contains
|
||||
ESX.Table.Sort = xLib.table.sort
|
||||
ESX.Table.ToArray = xLib.table.toArray
|
||||
ESX.Table.Wipe = xLib.table.wipe
|
||||
@@ -178,45 +178,6 @@ function ESX.IsFunctionReference(val)
|
||||
return typeVal == "function" or (typeVal == "table" and type(getmetatable(val)?.__call) == "function")
|
||||
end
|
||||
|
||||
---@param conditionFunc function A function that is repeatedly called until it returns a truthy value or the timeout is exceeded.
|
||||
---@param errorMessage? string Optional. If set, an error will be thrown with this message if the condition is not met within the timeout. If not set, no error will be thrown.
|
||||
---@param timeoutMs? number Optional. The maximum time to wait (in milliseconds) for the condition to be met. Defaults to 1000ms.
|
||||
---@return boolean, any: Returns success status and the returned value of the condition function.
|
||||
function ESX.Await(conditionFunc, errorMessage, timeoutMs)
|
||||
timeoutMs = timeoutMs or 1000
|
||||
|
||||
if timeoutMs < 0 then
|
||||
error("Timeout should be a positive number.")
|
||||
end
|
||||
|
||||
if not ESX.IsFunctionReference(conditionFunc) then
|
||||
error("Condition Function should be a function reference.")
|
||||
end
|
||||
|
||||
-- since errorMessage is optional, we only validate it if the user provided it.
|
||||
if errorMessage then
|
||||
ESX.AssertType(errorMessage, "string", "errorMessage should be a string.")
|
||||
end
|
||||
|
||||
local invokingResource = GetInvokingResource()
|
||||
local startTimeMs = GetGameTimer()
|
||||
while GetGameTimer() - startTimeMs < timeoutMs do
|
||||
local result = conditionFunc()
|
||||
|
||||
if result then
|
||||
return true, result
|
||||
end
|
||||
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
if errorMessage then
|
||||
error(("[%s] -> %s"):format(invokingResource, errorMessage))
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---@param str string
|
||||
---@param allowDigits boolean? Allow numbers if necessary
|
||||
---@return boolean
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
ESX.Table = {}
|
||||
|
||||
-- nil proof alternative to #table
|
||||
---@param t table
|
||||
---@return number
|
||||
function ESX.Table.SizeOf(t)
|
||||
local count = 0
|
||||
|
||||
for _, _ in pairs(t) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function ESX.Table.Set(t)
|
||||
local set = {}
|
||||
for _, v in ipairs(t) do
|
||||
set[v] = true
|
||||
end
|
||||
return set
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param value any
|
||||
---@return number
|
||||
function ESX.Table.IndexOf(t, value)
|
||||
for i = 1, #t, 1 do
|
||||
if t[i] == value then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param value any
|
||||
---@return number
|
||||
function ESX.Table.LastIndexOf(t, value)
|
||||
for i = #t, 1, -1 do
|
||||
if t[i] == value then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return any
|
||||
function ESX.Table.Find(t, cb)
|
||||
for i = 1, #t, 1 do
|
||||
if cb(t[i]) then
|
||||
return t[i]
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return number
|
||||
function ESX.Table.FindIndex(t, cb)
|
||||
for i = 1, #t, 1 do
|
||||
if cb(t[i]) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return table
|
||||
function ESX.Table.Filter(t, cb)
|
||||
local newTable = {}
|
||||
|
||||
for i = 1, #t, 1 do
|
||||
if cb(t[i]) then
|
||||
newTable[#newTable + 1] = t[i]
|
||||
end
|
||||
end
|
||||
|
||||
return newTable
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return table
|
||||
function ESX.Table.Map(t, cb)
|
||||
local newTable = {}
|
||||
|
||||
for i = 1, #t, 1 do
|
||||
newTable[i] = cb(t[i], i)
|
||||
end
|
||||
|
||||
return newTable
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function ESX.Table.Reverse(t)
|
||||
local newTable = {}
|
||||
|
||||
for i = #t, 1, -1 do
|
||||
table.insert(newTable, t[i])
|
||||
end
|
||||
|
||||
return newTable
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function ESX.Table.Clone(t)
|
||||
if type(t) ~= "table" then
|
||||
return t
|
||||
end
|
||||
|
||||
local meta = getmetatable(t)
|
||||
local target = {}
|
||||
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == "table" then
|
||||
target[k] = ESX.Table.Clone(v)
|
||||
else
|
||||
target[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(target, meta)
|
||||
|
||||
return target
|
||||
end
|
||||
|
||||
---@param t1 table
|
||||
---@param t2 table
|
||||
---@return table
|
||||
function ESX.Table.Concat(t1, t2)
|
||||
local t3 = ESX.Table.Clone(t1)
|
||||
|
||||
for i = 1, #t2, 1 do
|
||||
table.insert(t3, t2[i])
|
||||
end
|
||||
|
||||
return t3
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param sep string
|
||||
---@return string
|
||||
function ESX.Table.Join(t, sep)
|
||||
local str = ""
|
||||
|
||||
for i = 1, #t, 1 do
|
||||
if i > 1 then
|
||||
str = str .. (sep or ",")
|
||||
end
|
||||
|
||||
str = str .. t[i]
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
-- Credits: https://github.com/JonasDev99/qb-garages/blob/b0335d67cb72a6b9ac60f62a87fb3946f5c2f33d/server/main.lua#L5
|
||||
---@param tab table
|
||||
---@param val any
|
||||
---@return boolean
|
||||
function ESX.Table.TableContains(tab, val)
|
||||
if type(val) == "table" then
|
||||
for _, value in pairs(tab) do
|
||||
if ESX.Table.TableContains(val, value) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
else
|
||||
for _, value in pairs(tab) do
|
||||
if value == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Credit: https://stackoverflow.com/a/15706820
|
||||
-- Description: sort function for pairs
|
||||
---@param t table
|
||||
---@param order function
|
||||
---@return function
|
||||
function ESX.Table.Sort(t, order)
|
||||
-- collect the keys
|
||||
local keys = {}
|
||||
|
||||
for k, _ in pairs(t) do
|
||||
keys[#keys + 1] = k
|
||||
end
|
||||
|
||||
-- if order function given, sort by it by passing the table and keys a, b,
|
||||
-- otherwise just sort the keys
|
||||
if order then
|
||||
table.sort(keys, function(a, b)
|
||||
return order(t, a, b)
|
||||
end)
|
||||
else
|
||||
table.sort(keys)
|
||||
end
|
||||
|
||||
-- return the iterator function
|
||||
local i = 0
|
||||
|
||||
return function()
|
||||
i = i + 1
|
||||
if keys[i] then
|
||||
return keys[i], t[keys[i]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return Array
|
||||
function ESX.Table.ToArray(t)
|
||||
local array = {}
|
||||
for _, v in pairs(t) do
|
||||
array[#array + 1] = v
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function ESX.Table.Wipe(t)
|
||||
return table.wipe(t)
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
|
||||
use_experimental_fxv2_oal 'yes'
|
||||
lua54 'yes'
|
||||
|
||||
author 'ESX Team'
|
||||
version '0.01'
|
||||
description 'Official ESX library'
|
||||
|
||||
files {
|
||||
'imports.lua',
|
||||
'imports/**/client.lua',
|
||||
'imports/**/shared.lua',
|
||||
}
|
||||
|
||||
shared_scripts {
|
||||
'resource/init.lua',
|
||||
'resource/**/shared.lua',
|
||||
}
|
||||
|
||||
client_scripts {
|
||||
'resource/**/client.lua',
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
'resource/**/server.lua',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
--! DISCLAIMER
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
-- Const Variables
|
||||
local CONTEXT<const> = IsDuplicityVersion() and 'server' or 'client'
|
||||
local LIB_NAME <const> = 'esx_lib'
|
||||
local IS_DEBUG <const> = GetConvar('xLib:debug', 'false') == 'true'
|
||||
|
||||
|
||||
---@alias Module table | function
|
||||
---@class xLib
|
||||
---@field name string
|
||||
---@field side 'server' | 'client'
|
||||
|
||||
-------------------------------------------------
|
||||
--- Core functions for modules
|
||||
-------------------------------------------------
|
||||
|
||||
--- Function that returns nothing
|
||||
--- Used as a "shortcut"
|
||||
function Noop() end
|
||||
|
||||
---Loads a module into memory
|
||||
---@param self xLib
|
||||
---@param name string -- module name
|
||||
---@return Module | nil
|
||||
local function loadModule(self, name)
|
||||
local directory = ('imports/%s'):format(name)
|
||||
local chunk = LoadResourceFile(LIB_NAME, ('%s/%s.lua'):format(directory, CONTEXT))
|
||||
local shared_chunk = LoadResourceFile(LIB_NAME, ('%s/shared.lua'):format(directory))
|
||||
|
||||
if shared_chunk then
|
||||
chunk = chunk and ('%s\n%s'):format(shared_chunk, chunk) or shared_chunk
|
||||
end
|
||||
|
||||
if not chunk then
|
||||
return
|
||||
end
|
||||
|
||||
-- Second argument is a chunk name
|
||||
local fn, err = load(chunk, ('@@%s/imports/%s/%s.lua'):format(LIB_NAME, name, CONTEXT))
|
||||
|
||||
if not fn or err then
|
||||
if shared_chunk then
|
||||
print(('[%s] Found an error while importing - %s! Try updating %s to the latest version!'):format(LIB_NAME,
|
||||
name, LIB_NAME))
|
||||
|
||||
-- Tries to load only shared.lua
|
||||
fn, err = load(shared_chunk, ('@@%s/imports/%s/%s.lua'):format(LIB_NAME, name, 'shared'))
|
||||
end
|
||||
|
||||
if not fn or err then
|
||||
return error(('Completly failed importing module - %s; error - %s'):format(name, err))
|
||||
end
|
||||
end
|
||||
|
||||
local result = fn()
|
||||
self[name] = result or Noop
|
||||
|
||||
return self[name]
|
||||
end
|
||||
|
||||
---Function that is responsible for lazy loading
|
||||
---@param self xLib
|
||||
---@param index string -- name of the function/key that is called
|
||||
---@param ... unknown -- function params
|
||||
---@return Module
|
||||
local function call(self, index, ...)
|
||||
local module = rawget(self, index) -- uses rawget not to trigger any metamethods
|
||||
|
||||
-- Module Loading
|
||||
if not module then
|
||||
self[index] = Noop -- to prevent module from loading again if doesn't exists.
|
||||
|
||||
module = loadModule(self, index)
|
||||
|
||||
if not module then
|
||||
local function method(...)
|
||||
return exports[LIB_NAME][index](nil, ...)
|
||||
end
|
||||
|
||||
if not ... then
|
||||
self[index] = method
|
||||
end
|
||||
|
||||
return method
|
||||
end
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
--- Environment Setup
|
||||
-------------------------------------------------
|
||||
|
||||
-- Creation of xLib object
|
||||
---@diagnostic disable-next-line: lowercase-global
|
||||
local xLib = setmetatable({
|
||||
name = LIB_NAME,
|
||||
side = CONTEXT,
|
||||
debug = IS_DEBUG
|
||||
}, {
|
||||
-- Lazy Loading - loads module only when accessed
|
||||
__index = call,
|
||||
__call = call,
|
||||
})
|
||||
|
||||
-- Allows to xLib to be accessible in resource that imports a lib
|
||||
_ENV.xLib = xLib
|
||||
_ENV.require = xLib.require
|
||||
@@ -0,0 +1,92 @@
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
---@class KeybindProps
|
||||
---@field name string
|
||||
---@field description string
|
||||
---@field defaultMapper? string (see: https://docs.fivem.net/docs/game-references/input-mapper-parameter-ids/)
|
||||
---@field defaultKey? string
|
||||
---@field disabled? boolean
|
||||
---@field disable? fun(self: CKeybind, toggle: boolean)
|
||||
---@field onPressed? fun(self: CKeybind)
|
||||
---@field onReleased? fun(self: CKeybind)
|
||||
---@field remove? fun(self: CKeybind)
|
||||
---@field [string] any
|
||||
|
||||
---@class CKeybind : KeybindProps
|
||||
---@field currentKey string
|
||||
---@field disabled boolean
|
||||
---@field isPressed boolean
|
||||
---@field hash number
|
||||
---@field getCurrentKey fun(): string
|
||||
---@field isControlPressed fun(): boolean
|
||||
|
||||
local keybinds = {}
|
||||
|
||||
local IsPauseMenuActive = IsPauseMenuActive
|
||||
local GetControlInstructionalButton = GetControlInstructionalButton
|
||||
|
||||
local keybind_mt = {
|
||||
disabled = false,
|
||||
isPressed = false,
|
||||
defaultKey = '',
|
||||
defaultMapper = 'keyboard',
|
||||
}
|
||||
|
||||
function keybind_mt:__index(index)
|
||||
return index == 'currentKey' and self:getCurrentKey() or keybind_mt[index]
|
||||
end
|
||||
|
||||
function keybind_mt:getCurrentKey()
|
||||
return GetControlInstructionalButton(0, self.hash, true):sub(3)
|
||||
end
|
||||
|
||||
function keybind_mt:isControlPressed()
|
||||
return self.isPressed
|
||||
end
|
||||
|
||||
function keybind_mt:disable(toggle)
|
||||
self.disabled = toggle
|
||||
end
|
||||
|
||||
function keybind_mt:remove()
|
||||
if not keybinds[self.name] then return end
|
||||
keybinds[self.name] = nil
|
||||
end
|
||||
|
||||
---@param data KeybindProps
|
||||
---@return CKeybind
|
||||
function xLib.addKeybind(data)
|
||||
data.hash = joaat("+" .. data.name) | 0x80000000
|
||||
keybinds[data.name] = setmetatable(data, keybind_mt)
|
||||
|
||||
RegisterCommand('+' .. data.name, function()
|
||||
if not keybinds[data.name] then return end
|
||||
if data.disabled or IsPauseMenuActive() then return end
|
||||
data.isPressed = true
|
||||
if data.onPressed then data:onPressed() end
|
||||
end)
|
||||
|
||||
RegisterCommand('-' .. data.name, function()
|
||||
if not keybinds[data.name] then return end
|
||||
if data.disabled or IsPauseMenuActive() then return end
|
||||
data.isPressed = false
|
||||
if data.onReleased then data:onReleased() end
|
||||
end)
|
||||
|
||||
RegisterKeyMapping('+' .. data.name, data.description, data.defaultMapper, data.defaultKey)
|
||||
|
||||
SetTimeout(500, function()
|
||||
TriggerEvent('chat:removeSuggestion', ('/+%s'):format(data.name))
|
||||
TriggerEvent('chat:removeSuggestion', ('/-%s'):format(data.name))
|
||||
end)
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
return xLib.addKeybind
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
--[[
|
||||
Generic client-side player state cache, modelled on ox_lib cache.
|
||||
Framework agnostic: natives only, no ESX coupling.
|
||||
|
||||
Loaded per-resource VM through the lazy loader. PlayerPedId() and friends
|
||||
return the same value in every client VM, so the cached values are correct
|
||||
in each resource that requires it.
|
||||
|
||||
Exposes the live values as xLib.cache and emits `xLib:cache:<key>` (value, previous)
|
||||
whenever a tracked value changes. `coords` is read on demand from the live ped
|
||||
and never stored.
|
||||
]]
|
||||
|
||||
local playerId = PlayerId()
|
||||
|
||||
local cache = setmetatable({
|
||||
playerId = playerId,
|
||||
ped = PlayerPedId(),
|
||||
vehicle = false,
|
||||
seat = false,
|
||||
weapon = false,
|
||||
}, {
|
||||
__index = function(self, key)
|
||||
if key == "coords" then
|
||||
return GetEntityCoords(self.ped)
|
||||
elseif key == "serverId" then
|
||||
-- resolved lazily: GetPlayerServerId can return -1 before the network
|
||||
-- session is ready, so only cache it once it is valid.
|
||||
local id = GetPlayerServerId(playerId)
|
||||
if id and id ~= -1 then
|
||||
rawset(self, "serverId", id)
|
||||
end
|
||||
return id
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local function set(key, value)
|
||||
if cache[key] == value then
|
||||
return
|
||||
end
|
||||
|
||||
local previous = cache[key]
|
||||
rawset(cache, key, value)
|
||||
TriggerEvent(("xLib:cache:%s"):format(key), value, previous)
|
||||
end
|
||||
|
||||
local function getSeat(ped, vehicle)
|
||||
for seat = -1, 16 do
|
||||
if GetPedInVehicleSeat(vehicle, seat) == ped then
|
||||
return seat
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
local ped = PlayerPedId()
|
||||
if ped ~= cache.ped then
|
||||
set("ped", ped)
|
||||
end
|
||||
|
||||
---@type integer|false
|
||||
local vehicle = GetVehiclePedIsIn(ped, false)
|
||||
vehicle = vehicle ~= 0 and vehicle or false
|
||||
|
||||
if vehicle ~= cache.vehicle then
|
||||
set("vehicle", vehicle)
|
||||
set("seat", vehicle and getSeat(ped, vehicle) or false)
|
||||
elseif vehicle then
|
||||
local seat = getSeat(ped, vehicle)
|
||||
if seat ~= cache.seat then
|
||||
set("seat", seat)
|
||||
end
|
||||
end
|
||||
|
||||
---@type integer|false
|
||||
local weapon = GetSelectedPedWeapon(ped)
|
||||
weapon = weapon ~= `WEAPON_UNARMED` and weapon or false
|
||||
if weapon ~= cache.weapon then
|
||||
set("weapon", weapon)
|
||||
end
|
||||
|
||||
Wait(100)
|
||||
end
|
||||
end)
|
||||
|
||||
return cache
|
||||
@@ -0,0 +1,148 @@
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
local pendingCallbacks = {}
|
||||
local timers = {}
|
||||
local cbEvent = '__xLib_cb_%s'
|
||||
local callbackTimeout = GetConvarInt('xLib:callbackTimeout', 300000)
|
||||
local resource_name = GetCurrentResourceName() --TODO: Add cache
|
||||
|
||||
RegisterNetEvent(cbEvent:format(resource_name), function(key, ...)
|
||||
if source == '' then return end
|
||||
|
||||
local cb = pendingCallbacks[key]
|
||||
|
||||
if not cb then return end
|
||||
|
||||
pendingCallbacks[key] = nil
|
||||
|
||||
cb(...)
|
||||
end)
|
||||
|
||||
---@param event string
|
||||
---@param delay? number | false prevent the event from being called for the given time
|
||||
local function eventTimer(event, delay)
|
||||
if xLib.verify(delay, 'number') then
|
||||
if delay > 0 then
|
||||
local time = GetGameTimer()
|
||||
|
||||
if (timers[event] or 0) > time then
|
||||
return false
|
||||
end
|
||||
|
||||
timers[event] = time + delay
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@param _ any
|
||||
---@param event string
|
||||
---@param delay number | false | nil
|
||||
---@param cb function | false
|
||||
---@param ... any
|
||||
---@return ...
|
||||
local function triggerServerCallback(_, event, delay, cb, ...)
|
||||
if not eventTimer(event, delay) then return end
|
||||
|
||||
local key
|
||||
|
||||
repeat
|
||||
key = ('%s:%s'):format(event, math.random(0, 100000))
|
||||
until not pendingCallbacks[key]
|
||||
|
||||
TriggerServerEvent('xLib:validateCallback', event, resource_name, key)
|
||||
TriggerServerEvent(cbEvent:format(event), resource_name, key, ...)
|
||||
|
||||
---@type promise | false
|
||||
local promise = not cb and promise.new()
|
||||
|
||||
pendingCallbacks[key] = function(response, ...)
|
||||
if response == 'cb_invalid' then
|
||||
response = ("callback '%s' does not exist"):format(event)
|
||||
|
||||
return promise and promise:reject(response) or error(response)
|
||||
end
|
||||
|
||||
response = { response, ... }
|
||||
|
||||
if promise then
|
||||
return promise:resolve(response)
|
||||
end
|
||||
|
||||
if cb then
|
||||
cb(table.unpack(response))
|
||||
end
|
||||
end
|
||||
|
||||
if promise then
|
||||
SetTimeout(callbackTimeout, function() promise:reject(("callback event '%s' timed out"):format(key)) end)
|
||||
|
||||
return table.unpack(Citizen.Await(promise))
|
||||
end
|
||||
end
|
||||
|
||||
---@overload fun(event: string, delay: number | false, cb: function, ...)
|
||||
xLib.callback = setmetatable({}, {
|
||||
__call = function(_, event, delay, cb, ...)
|
||||
if not cb then
|
||||
warn(("callback event '%s' does not have a function to callback to and will instead await\nuse xLib.callback.await or a regular event to remove this warning")
|
||||
:format(event))
|
||||
else
|
||||
local cbType = type(cb)
|
||||
|
||||
if cbType == 'table' and getmetatable(cb)?.__call then
|
||||
cbType = 'function'
|
||||
end
|
||||
|
||||
xLib.verify(cbType, 'function', true)
|
||||
end
|
||||
|
||||
return triggerServerCallback(_, event, delay, cb, ...)
|
||||
end
|
||||
})
|
||||
|
||||
---@param event string
|
||||
---@param delay? number | false prevent the event from being called for the given time.
|
||||
---Sends an event to the server and halts the current thread until a response is returned.
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function xLib.callback.await(event, delay, ...)
|
||||
return triggerServerCallback(nil, event, delay, false, ...)
|
||||
end
|
||||
|
||||
local function callbackResponse(success, result, ...)
|
||||
if not success then
|
||||
if result then
|
||||
return print(('^1SCRIPT ERROR: %s^0\n%s'):format(result,
|
||||
Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()) or ''))
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return result, ...
|
||||
end
|
||||
|
||||
local pcall = pcall
|
||||
|
||||
---@param name string
|
||||
---@param cb function
|
||||
---Registers an event handler and callback function to respond to server requests.
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function xLib.callback.register(name, cb)
|
||||
local event = cbEvent:format(name)
|
||||
|
||||
xLib.setValidCallback(name, true)
|
||||
|
||||
RegisterNetEvent(event, function(resource, key, ...)
|
||||
TriggerServerEvent(cbEvent:format(resource), key, callbackResponse(pcall(cb, ...)))
|
||||
end)
|
||||
end
|
||||
|
||||
return xLib.callback
|
||||
@@ -0,0 +1,127 @@
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
local pendingCallbacks = {}
|
||||
local cbEvent = '__xLib_cb_%s'
|
||||
local callbackTimeout = GetConvarInt('xLib:callbackTimeout', 300000)
|
||||
local resource_name = GetCurrentResourceName() --TODO: Add cache
|
||||
|
||||
RegisterNetEvent(cbEvent:format(resource_name), function(key, ...)
|
||||
local cb = pendingCallbacks[key]
|
||||
|
||||
if not cb then return end
|
||||
|
||||
pendingCallbacks[key] = nil
|
||||
|
||||
cb(...)
|
||||
end)
|
||||
|
||||
---@param _ any
|
||||
---@param event string
|
||||
---@param playerId number
|
||||
---@param cb function|false
|
||||
---@param ... any
|
||||
---@return ...
|
||||
local function triggerClientCallback(_, event, playerId, cb, ...)
|
||||
xLib.verify(playerId, 'playerId', true)
|
||||
|
||||
local key
|
||||
|
||||
repeat
|
||||
key = ('%s:%s:%s'):format(event, math.random(0, 100000), playerId)
|
||||
until not pendingCallbacks[key]
|
||||
|
||||
TriggerClientEvent('xLib:validateCallback', playerId, event, resource_name, key)
|
||||
TriggerClientEvent(cbEvent:format(event), playerId, resource_name, key, ...)
|
||||
|
||||
---@type promise | false
|
||||
local promise = not cb and promise.new()
|
||||
|
||||
pendingCallbacks[key] = function(response, ...)
|
||||
if response == 'cb_invalid' then
|
||||
response = ("callback '%s' does not exist"):format(event)
|
||||
|
||||
return promise and promise:reject(response) or error(response)
|
||||
end
|
||||
|
||||
response = { response, ... }
|
||||
|
||||
if promise then
|
||||
return promise:resolve(response)
|
||||
end
|
||||
|
||||
if cb then
|
||||
cb(table.unpack(response))
|
||||
end
|
||||
end
|
||||
|
||||
if promise then
|
||||
SetTimeout(callbackTimeout, function() promise:reject(("callback event '%s' timed out"):format(key)) end)
|
||||
|
||||
return table.unpack(Citizen.Await(promise))
|
||||
end
|
||||
end
|
||||
|
||||
---@overload fun(event: string, playerId: number, cb: function, ...)
|
||||
xLib.callback = setmetatable({}, {
|
||||
__call = function(_, event, playerId, cb, ...)
|
||||
if not cb then
|
||||
warn(("callback event '%s' does not have a function to callback to and will instead await\nuse xLib.callback.await or a regular event to remove this warning")
|
||||
:format(event))
|
||||
else
|
||||
local cbType = type(cb)
|
||||
|
||||
if cbType == 'table' and getmetatable(cb)?.__call then
|
||||
cbType = 'function'
|
||||
end
|
||||
|
||||
xLib.verify(cbType, 'function', true)
|
||||
end
|
||||
|
||||
return triggerClientCallback(_, event, playerId, cb, ...)
|
||||
end
|
||||
})
|
||||
|
||||
---@param event string
|
||||
---@param playerId number
|
||||
--- Sends an event to a client and halts the current thread until a response is returned.
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function xLib.callback.await(event, playerId, ...)
|
||||
return triggerClientCallback(nil, event, playerId, false, ...)
|
||||
end
|
||||
|
||||
local function callbackResponse(success, result, ...)
|
||||
if not success then
|
||||
if result then
|
||||
return print(('^1SCRIPT ERROR: %s^0\n%s'):format(result,
|
||||
Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()) or ''))
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return result, ...
|
||||
end
|
||||
|
||||
local pcall = pcall
|
||||
|
||||
---@param name string
|
||||
---@param cb function
|
||||
---Registers an event handler and callback function to respond to client requests.
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function xLib.callback.register(name, cb)
|
||||
event = cbEvent:format(name)
|
||||
|
||||
xLib.setValidCallback(name, true)
|
||||
|
||||
RegisterNetEvent(event, function(resource, key, ...)
|
||||
TriggerClientEvent(cbEvent:format(resource), source, key, callbackResponse(pcall(cb, source, ...)))
|
||||
end)
|
||||
end
|
||||
|
||||
return xLib.callback
|
||||
@@ -0,0 +1,35 @@
|
||||
---@class xClass
|
||||
---@field new function
|
||||
---@field constructor function?
|
||||
|
||||
---Create or register a class
|
||||
---@param copy? table
|
||||
---@return table
|
||||
function xLib.class(copy)
|
||||
xLib.verify(copy, { 'table', 'nil' }, true)
|
||||
|
||||
local class = copy and xLib.table.deepcopy(copy) or {}
|
||||
|
||||
class.__index = class
|
||||
class._isClass = true
|
||||
|
||||
setmetatable(class, {
|
||||
__newindex = function (_, k,v)
|
||||
rawset(class, k, v)
|
||||
end
|
||||
})
|
||||
|
||||
function class:new(...)
|
||||
local obj = setmetatable({}, class)
|
||||
|
||||
if xLib.verify(obj.constructor, 'function') then
|
||||
obj:constructor(...)
|
||||
end
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return class
|
||||
end
|
||||
|
||||
return xLib.class
|
||||
@@ -0,0 +1,10 @@
|
||||
xLib.colors = {}
|
||||
|
||||
xLib.colors.brand = GetConvar("esx:brand-color", "#FB9B04")
|
||||
xLib.colors.darkest = GetConvar("esx:darkest-color", "#161616")
|
||||
xLib.colors.dark = GetConvar("esx:dark-color", "#252525")
|
||||
xLib.colors.mid = GetConvar("esx:mid-color", "#383838")
|
||||
xLib.colors.light = GetConvar("esx:light-color", "#969696")
|
||||
xLib.colors.lightest = GetConvar("esx:lightest-color", "#F2F2F2")
|
||||
|
||||
return xLib.colors
|
||||
@@ -0,0 +1,120 @@
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
---@class DuiProperties
|
||||
---@field url string
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field debug? boolean
|
||||
|
||||
---@class Dui
|
||||
---@field private_id string
|
||||
---@field private_debug boolean
|
||||
---@field url string
|
||||
---@field duiObject number
|
||||
---@field duiHandle string
|
||||
---@field runtimeTxd number
|
||||
---@field txdObject number
|
||||
---@field dictName string
|
||||
---@field txtName string
|
||||
xLib.dui = xLib.class()
|
||||
|
||||
local resource<const> = GetCurrentResourceName()
|
||||
|
||||
---@type table<string, Dui>
|
||||
local Duis = {}
|
||||
|
||||
local currentId = 0
|
||||
|
||||
---@param data DuiProperties
|
||||
function xLib.dui:constructor(data)
|
||||
local time = GetGameTimer()
|
||||
local id = ("%s_%s_%s"):format(resource, time, currentId)
|
||||
currentId = currentId + 1
|
||||
local dictName = ("%s_dui_dict_%s"):format(resource, id)
|
||||
local txtName = ("%s_lib_dui_txt_%s"):format(resource, id)
|
||||
local duiObject = CreateDui(data.url, data.width, data.height)
|
||||
local duiHandle = GetDuiHandle(duiObject)
|
||||
local runtimeTxd = CreateRuntimeTxd(dictName)
|
||||
local txdObject = CreateRuntimeTextureFromDuiHandle(runtimeTxd, txtName, duiHandle)
|
||||
self.private_id = id
|
||||
self.private_debug = data.debug or false
|
||||
self.url = data.url
|
||||
self.duiObject = duiObject
|
||||
self.duiHandle = duiHandle
|
||||
self.runtimeTxd = runtimeTxd
|
||||
self.txdObject = txdObject
|
||||
self.dictName = dictName
|
||||
self.txtName = txtName
|
||||
Duis[id] = self
|
||||
|
||||
if self.private_debug then
|
||||
print(("Dui %s created"):format(id))
|
||||
end
|
||||
end
|
||||
|
||||
function xLib.dui:remove()
|
||||
SetDuiUrl(self.duiObject, "about:blank")
|
||||
DestroyDui(self.duiObject)
|
||||
Duis[self.private_id] = nil
|
||||
|
||||
if self.private_debug then
|
||||
print(("Dui %s removed"):format(self.private_id))
|
||||
end
|
||||
end
|
||||
|
||||
---@param url string
|
||||
function xLib.dui:setUrl(url)
|
||||
self.url = url
|
||||
SetDuiUrl(self.duiObject, url)
|
||||
|
||||
if self.private_debug then
|
||||
print(("Dui %s url set to %s"):format(self.private_id, url))
|
||||
end
|
||||
end
|
||||
|
||||
---@param message table
|
||||
function xLib.dui:sendMessage(message)
|
||||
SendDuiMessage(self.duiObject, json.encode(message))
|
||||
|
||||
if self.private_debug then
|
||||
print(("Dui %s message sent with data :"):format(self.private_id), json.encode(message, { indent = true }))
|
||||
end
|
||||
end
|
||||
|
||||
---@param x number
|
||||
---@param y number
|
||||
function xLib.dui:sendMouseMove(x, y)
|
||||
SendDuiMouseMove(self.duiObject, x, y)
|
||||
end
|
||||
|
||||
---@param button "left" | "middle" | "right"
|
||||
function xLib.dui:sendMouseDown(button)
|
||||
SendDuiMouseDown(self.duiObject, button)
|
||||
end
|
||||
|
||||
---@param button "left" | "middle" | "right"
|
||||
function xLib.dui:sendMouseUp(button)
|
||||
SendDuiMouseUp(self.duiObject, button)
|
||||
end
|
||||
|
||||
---@param deltaX number
|
||||
---@param deltaY number
|
||||
function xLib.dui:sendMouseWheel(deltaX, deltaY)
|
||||
SendDuiMouseWheel(self.duiObject, deltaY, deltaX)
|
||||
end
|
||||
|
||||
AddEventHandler("onResourceStop", function(resourceName)
|
||||
if resource ~= resourceName then return end
|
||||
|
||||
for id in next, Duis do
|
||||
Duis[id]:remove()
|
||||
end
|
||||
end)
|
||||
|
||||
return xLib.dui
|
||||
@@ -0,0 +1,85 @@
|
||||
xLib.entity = {}
|
||||
|
||||
---@param entities table The entities to search through
|
||||
---@param isPlayerEntities boolean Whether the entities are players
|
||||
---@param coords? table | vector3 The coords to search from
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function xLib.entity.closest(entities, isPlayerEntities, coords, modelFilter)
|
||||
local closestEntity, closestEntityDistance, filteredEntities = -1, -1, nil
|
||||
|
||||
if coords then
|
||||
coords = vector3(coords.x, coords.y, coords.z)
|
||||
else
|
||||
local playerPed = PlayerPedId()
|
||||
coords = GetEntityCoords(playerPed)
|
||||
end
|
||||
|
||||
if modelFilter then
|
||||
filteredEntities = {}
|
||||
|
||||
for currentEntityIndex = 1, #entities do
|
||||
if modelFilter[GetEntityModel(entities[currentEntityIndex])] then
|
||||
filteredEntities[#filteredEntities + 1] = entities[currentEntityIndex]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k, entity in pairs(filteredEntities or entities) do
|
||||
local distance = #(coords - GetEntityCoords(entity))
|
||||
|
||||
if closestEntityDistance == -1 or distance < closestEntityDistance then
|
||||
closestEntity, closestEntityDistance = isPlayerEntities and k or entity, distance
|
||||
end
|
||||
end
|
||||
|
||||
return closestEntity, closestEntityDistance
|
||||
end
|
||||
|
||||
---@param entities table The entities to search through
|
||||
---@param isPlayerEntities boolean Whether the entities are players
|
||||
---@param coords? table | vector3 The coords to search from
|
||||
---@param maxDistance number The maximum distance
|
||||
---@return table
|
||||
function xLib.entity.EnumerateWithinDistance(entities, isPlayerEntities, coords, maxDistance)
|
||||
local nearbyEntities = {}
|
||||
|
||||
if coords then
|
||||
coords = vector3(coords.x, coords.y, coords.z)
|
||||
else
|
||||
local playerPed = PlayerPedId()
|
||||
coords = GetEntityCoords(playerPed)
|
||||
end
|
||||
|
||||
for k, entity in pairs(entities) do
|
||||
local distance = #(coords - GetEntityCoords(entity))
|
||||
|
||||
if distance <= maxDistance then
|
||||
nearbyEntities[#nearbyEntities + 1] = isPlayerEntities and k or entity
|
||||
end
|
||||
end
|
||||
|
||||
return nearbyEntities
|
||||
end
|
||||
|
||||
---@param entity integer The entity to get the coords of
|
||||
---@param coords table | vector3 | vector4 The coords to teleport the entity to
|
||||
---@param cb? function The callback function
|
||||
function xLib.entity.Teleport(entity, coords, cb)
|
||||
|
||||
if DoesEntityExist(entity) then
|
||||
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
|
||||
while not HasCollisionLoadedAroundEntity(entity) do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
SetEntityCoords(entity, coords.x, coords.y, coords.z, false, false, false, false)
|
||||
SetEntityHeading(entity, coords.w or coords.heading or 0.0)
|
||||
end
|
||||
|
||||
if cb then
|
||||
cb()
|
||||
end
|
||||
end
|
||||
|
||||
return xLib.entity
|
||||
@@ -0,0 +1,695 @@
|
||||
---@class gamelib
|
||||
xLib.game = {}
|
||||
|
||||
local function isCallable(val)
|
||||
local valType = type(val)
|
||||
local mt = getmetatable(val)
|
||||
return valType == "function" or (valType == "table" and mt ~= nil and type(mt.__call) == "function")
|
||||
end
|
||||
|
||||
---@param ped integer The ped to get the mugshot of
|
||||
---@param transparent? boolean Whether the mugshot should be transparent
|
||||
function xLib.game.getPedMugshot(ped, transparent)
|
||||
if not DoesEntityExist(ped) then
|
||||
return
|
||||
end
|
||||
|
||||
local mugshot = transparent and RegisterPedheadshotTransparent(ped) or RegisterPedheadshot(ped)
|
||||
|
||||
while not IsPedheadshotReady(mugshot) do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
return mugshot, GetPedheadshotTxdString(mugshot)
|
||||
end
|
||||
|
||||
---@param object integer | string The object to spawn
|
||||
---@param coords table | vector3 The coords to spawn the object at
|
||||
---@param cb? function The callback function
|
||||
---@param networked? boolean Whether the object should be networked
|
||||
---@return integer | nil
|
||||
function xLib.game.spawnObject(object, coords, cb, networked)
|
||||
local model = type(object) == "number" and object or joaat(object)
|
||||
|
||||
xLib.streaming.requestModel(model)
|
||||
|
||||
local obj = CreateObject(model, coords.x, coords.y, coords.z, networked == nil or networked, false, true)
|
||||
return cb and cb(obj) or obj
|
||||
end
|
||||
|
||||
---@param object integer | string The object to spawn
|
||||
---@param coords table | vector3 The coords to spawn the object at
|
||||
---@param cb? function The callback function
|
||||
---@return nil
|
||||
function xLib.game.spawnLocalObject(object, coords, cb)
|
||||
xLib.game.spawnObject(object, coords, cb, false)
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to delete
|
||||
---@return nil
|
||||
function xLib.game.deleteVehicle(vehicle)
|
||||
SetEntityAsMissionEntity(vehicle, true, true)
|
||||
DeleteVehicle(vehicle)
|
||||
end
|
||||
|
||||
---@param object integer The object to delete
|
||||
---@return nil
|
||||
function xLib.game.deleteObject(object)
|
||||
SetEntityAsMissionEntity(object, false, true)
|
||||
DeleteObject(object)
|
||||
end
|
||||
|
||||
---@param vehicleModel integer | string The vehicle to spawn
|
||||
---@param coords table | vector3 The coords to spawn the vehicle at
|
||||
---@param heading number The heading of the vehicle
|
||||
---@param cb? fun(vehicle: number) The callback function
|
||||
---@param networked? boolean Whether the vehicle should be networked
|
||||
---@return number? vehicle
|
||||
function xLib.game.spawnVehicle(vehicleModel, coords, heading, cb, networked)
|
||||
if cb and not isCallable(cb) then
|
||||
error("Invalid callback function")
|
||||
end
|
||||
|
||||
local model = type(vehicleModel) == "number" and vehicleModel or joaat(vehicleModel)
|
||||
local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z)
|
||||
local isNetworked = networked == nil or networked
|
||||
|
||||
local playerCoords = GetEntityCoords(PlayerPedId())
|
||||
if not vector or not playerCoords then
|
||||
return
|
||||
end
|
||||
|
||||
local dist = #(playerCoords - vector)
|
||||
if dist > 424 then -- Onesync infinity Range (https://docs.fivem.net/docs/scripting-reference/onesync/)
|
||||
local executingResource = GetInvokingResource() or "Unknown"
|
||||
return error(("Resource ^5%s^1 Tried to spawn vehicle on the client but the position is too far away (Out of onesync range)."):format(executingResource))
|
||||
end
|
||||
|
||||
local promise = not cb and promise.new()
|
||||
CreateThread(function()
|
||||
local modelHash = xLib.streaming.requestModel(model)
|
||||
if not modelHash then
|
||||
if promise then
|
||||
promise:reject(("Tried to spawn invalid vehicle - ^5%s^7!"):format(model))
|
||||
return
|
||||
end
|
||||
error(("Tried to spawn invalid vehicle - ^5%s^7!"):format(model))
|
||||
end
|
||||
|
||||
local vehicle = CreateVehicle(model, vector.x, vector.y, vector.z, heading, isNetworked, true)
|
||||
|
||||
if networked then
|
||||
local id = NetworkGetNetworkIdFromEntity(vehicle)
|
||||
SetNetworkIdCanMigrate(id, true)
|
||||
SetEntityAsMissionEntity(vehicle, true, true)
|
||||
end
|
||||
SetVehicleHasBeenOwnedByPlayer(vehicle, true)
|
||||
SetVehicleNeedsToBeHotwired(vehicle, false)
|
||||
SetModelAsNoLongerNeeded(model)
|
||||
SetVehRadioStation(vehicle, "OFF")
|
||||
|
||||
RequestCollisionAtCoord(vector.x, vector.y, vector.z)
|
||||
while not HasCollisionLoadedAroundEntity(vehicle) do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
if promise then
|
||||
promise:resolve(vehicle)
|
||||
elseif cb then
|
||||
cb(vehicle)
|
||||
end
|
||||
end)
|
||||
|
||||
if promise then
|
||||
return Citizen.Await(promise)
|
||||
end
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to spawn
|
||||
---@param coords table | vector3 The coords to spawn the vehicle at
|
||||
---@param heading number The heading of the vehicle
|
||||
---@param cb? function The callback function
|
||||
---@return nil
|
||||
function xLib.game.spawnLocalVehicle(vehicle, coords, heading, cb)
|
||||
xLib.game.spawnVehicle(vehicle, coords, heading, cb, false)
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to check
|
||||
---@return boolean
|
||||
function xLib.game.isVehicleEmpty(vehicle)
|
||||
return GetVehicleNumberOfPassengers(vehicle) == 0 and IsVehicleSeatFree(vehicle, -1)
|
||||
end
|
||||
|
||||
---@return table
|
||||
function xLib.game.getObjects() -- Leave the function for compatibility
|
||||
return GetGamePool("CObject")
|
||||
end
|
||||
|
||||
---@param onlyOtherPeds? boolean Whether to exlude the player ped
|
||||
---@return table
|
||||
function xLib.game.getPeds(onlyOtherPeds)
|
||||
local pool = GetGamePool("CPed")
|
||||
|
||||
if onlyOtherPeds then
|
||||
local myPed = PlayerPedId()
|
||||
for i = 1, #pool do
|
||||
if pool[i] == myPed then
|
||||
table.remove(pool, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return pool
|
||||
end
|
||||
|
||||
---@return table
|
||||
function xLib.game.getVehicles() -- Leave the function for compatibility
|
||||
return GetGamePool("CVehicle")
|
||||
end
|
||||
|
||||
---@param onlyOtherPlayers? boolean Whether to exclude the player
|
||||
---@param returnKeyValue? boolean Whether to return the key value pair
|
||||
---@param returnPeds? boolean Whether to return the peds
|
||||
---@return table
|
||||
function xLib.game.getPlayers(onlyOtherPlayers, returnKeyValue, returnPeds)
|
||||
local players = {}
|
||||
local active = GetActivePlayers()
|
||||
|
||||
for i = 1, #active do
|
||||
local currentPlayer = active[i]
|
||||
local ped = GetPlayerPed(currentPlayer)
|
||||
|
||||
if DoesEntityExist(ped) and ((onlyOtherPlayers and currentPlayer ~= PlayerId()) or not onlyOtherPlayers) then
|
||||
if returnKeyValue then
|
||||
players[currentPlayer] = ped
|
||||
else
|
||||
players[#players + 1] = returnPeds and ped or currentPlayer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest object to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function xLib.game.getClosestObject(coords, modelFilter)
|
||||
return xLib.entity.closest(xLib.game.getObjects(), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest ped to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function xLib.game.getClosestPed(coords, modelFilter)
|
||||
return xLib.entity.closest(xLib.game.getPeds(true), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest player to
|
||||
---@return integer, integer
|
||||
function xLib.game.getClosestPlayer(coords)
|
||||
return xLib.entity.closest(xLib.game.getPlayers(true, true), true, coords, nil)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest vehicle to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function xLib.game.getClosestVehicle(coords, modelFilter)
|
||||
return xLib.entity.closest(xLib.game.getVehicles(), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return table
|
||||
function xLib.game.getPlayersInArea(coords, maxDistance)
|
||||
return xLib.entity.EnumerateWithinDistance(xLib.game.getPlayers(true, true), true, coords, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return table
|
||||
function xLib.game.getVehiclesInArea(coords, maxDistance)
|
||||
return xLib.entity.EnumerateWithinDistance(xLib.game.getVehicles(), false, coords, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return boolean
|
||||
function xLib.game.isSpawnPointClear(coords, maxDistance)
|
||||
return #xLib.game.getVehiclesInArea(coords, maxDistance) == 0
|
||||
end
|
||||
|
||||
---@return integer | nil, vector3 | nil
|
||||
function xLib.game.getVehicleInDirection()
|
||||
local _, hit, coords, _, _, entity = xLib.Raycast.FromScreen(5, 10, PlayerPedId())
|
||||
if hit and IsEntityAVehicle(entity) then
|
||||
return entity, coords
|
||||
end
|
||||
end
|
||||
|
||||
local function round(value, numDecimalPlaces)
|
||||
if numDecimalPlaces then
|
||||
local power = 10 ^ numDecimalPlaces
|
||||
return math.floor((value * power) + 0.5) / power
|
||||
else
|
||||
return math.floor(value + 0.5)
|
||||
end
|
||||
end
|
||||
|
||||
local function trim(value)
|
||||
value = tostring(value)
|
||||
return (string.gsub(value, "^%s*(.-)%s*$", "%1"))
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to get the properties of
|
||||
---@return table | nil
|
||||
function xLib.game.getVehicleProperties(vehicle)
|
||||
if not DoesEntityExist(vehicle) then
|
||||
return
|
||||
end
|
||||
|
||||
---@type number | number[], number | number[]
|
||||
local colorPrimary, colorSecondary = GetVehicleColours(vehicle)
|
||||
local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle)
|
||||
local dashboardColor = GetVehicleDashboardColor(vehicle)
|
||||
local interiorColor = GetVehicleInteriorColour(vehicle)
|
||||
|
||||
if GetIsVehiclePrimaryColourCustom(vehicle) then
|
||||
colorPrimary = { GetVehicleCustomPrimaryColour(vehicle) }
|
||||
end
|
||||
|
||||
if GetIsVehicleSecondaryColourCustom(vehicle) then
|
||||
colorSecondary = { GetVehicleCustomSecondaryColour(vehicle) }
|
||||
end
|
||||
|
||||
local hasCustomXenonColor, customXenonColorR, customXenonColorG, customXenonColorB = GetVehicleXenonLightsCustomColor(vehicle)
|
||||
local customXenonColor = nil
|
||||
if hasCustomXenonColor then
|
||||
customXenonColor = { customXenonColorR, customXenonColorG, customXenonColorB }
|
||||
end
|
||||
|
||||
local extras = {}
|
||||
for extraId = 0, 20 do
|
||||
if DoesExtraExist(vehicle, extraId) then
|
||||
extras[tostring(extraId)] = IsVehicleExtraTurnedOn(vehicle, extraId)
|
||||
end
|
||||
end
|
||||
|
||||
local doorsBroken, windowsBroken, tyreBurst = {}, {}, {}
|
||||
local numWheels = tostring(GetVehicleNumberOfWheels(vehicle))
|
||||
|
||||
local TyresIndex = { -- Wheel index list according to the number of vehicle wheels.
|
||||
["2"] = { 0, 4 }, -- Bike and cycle.
|
||||
["3"] = { 0, 1, 4, 5 }, -- Vehicle with 3 wheels (get for wheels because some 3 wheels vehicles have 2 wheels on front and one rear or the reverse).
|
||||
["4"] = { 0, 1, 4, 5 }, -- Vehicle with 4 wheels.
|
||||
["6"] = { 0, 1, 2, 3, 4, 5 }, -- Vehicle with 6 wheels.
|
||||
}
|
||||
|
||||
if TyresIndex[numWheels] then
|
||||
for _, idx in pairs(TyresIndex[numWheels]) do
|
||||
tyreBurst[tostring(idx)] = IsVehicleTyreBurst(vehicle, idx, false)
|
||||
end
|
||||
end
|
||||
|
||||
for windowId = 0, 7 do -- 13
|
||||
RollUpWindow(vehicle, windowId) --fix when you put the car away with the window down
|
||||
windowsBroken[tostring(windowId)] = not IsVehicleWindowIntact(vehicle, windowId)
|
||||
end
|
||||
|
||||
local numDoors = GetNumberOfVehicleDoors(vehicle)
|
||||
if numDoors and numDoors > 0 then
|
||||
for doorsId = 0, numDoors do
|
||||
doorsBroken[tostring(doorsId)] = IsVehicleDoorDamaged(vehicle, doorsId)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
model = GetEntityModel(vehicle),
|
||||
doorsBroken = doorsBroken,
|
||||
windowsBroken = windowsBroken,
|
||||
tyreBurst = tyreBurst,
|
||||
tyresCanBurst = GetVehicleTyresCanBurst(vehicle),
|
||||
plate = trim(GetVehicleNumberPlateText(vehicle)),
|
||||
plateIndex = GetVehicleNumberPlateTextIndex(vehicle),
|
||||
|
||||
bodyHealth = round(GetVehicleBodyHealth(vehicle), 1),
|
||||
engineHealth = round(GetVehicleEngineHealth(vehicle), 1),
|
||||
tankHealth = round(GetVehiclePetrolTankHealth(vehicle), 1),
|
||||
|
||||
fuelLevel = round(GetVehicleFuelLevel(vehicle), 1),
|
||||
dirtLevel = round(GetVehicleDirtLevel(vehicle), 1),
|
||||
color1 = colorPrimary,
|
||||
color2 = colorSecondary,
|
||||
|
||||
pearlescentColor = pearlescentColor,
|
||||
wheelColor = wheelColor,
|
||||
|
||||
dashboardColor = dashboardColor,
|
||||
interiorColor = interiorColor,
|
||||
|
||||
wheels = GetVehicleWheelType(vehicle),
|
||||
windowTint = GetVehicleWindowTint(vehicle),
|
||||
xenonColor = GetVehicleXenonLightsColor(vehicle),
|
||||
customXenonColor = customXenonColor,
|
||||
|
||||
neonEnabled = { IsVehicleNeonLightEnabled(vehicle, 0), IsVehicleNeonLightEnabled(vehicle, 1), IsVehicleNeonLightEnabled(vehicle, 2), IsVehicleNeonLightEnabled(vehicle, 3) },
|
||||
|
||||
neonColor = table.pack(GetVehicleNeonLightsColour(vehicle)),
|
||||
extras = extras,
|
||||
tyreSmokeColor = table.pack(GetVehicleTyreSmokeColor(vehicle)),
|
||||
|
||||
modSpoilers = GetVehicleMod(vehicle, 0),
|
||||
modFrontBumper = GetVehicleMod(vehicle, 1),
|
||||
modRearBumper = GetVehicleMod(vehicle, 2),
|
||||
modSideSkirt = GetVehicleMod(vehicle, 3),
|
||||
modExhaust = GetVehicleMod(vehicle, 4),
|
||||
modFrame = GetVehicleMod(vehicle, 5),
|
||||
modGrille = GetVehicleMod(vehicle, 6),
|
||||
modHood = GetVehicleMod(vehicle, 7),
|
||||
modFender = GetVehicleMod(vehicle, 8),
|
||||
modRightFender = GetVehicleMod(vehicle, 9),
|
||||
modRoof = GetVehicleMod(vehicle, 10),
|
||||
modRoofLivery = GetVehicleRoofLivery(vehicle),
|
||||
|
||||
modEngine = GetVehicleMod(vehicle, 11),
|
||||
modBrakes = GetVehicleMod(vehicle, 12),
|
||||
modTransmission = GetVehicleMod(vehicle, 13),
|
||||
modHorns = GetVehicleMod(vehicle, 14),
|
||||
modSuspension = GetVehicleMod(vehicle, 15),
|
||||
modArmor = GetVehicleMod(vehicle, 16),
|
||||
|
||||
modTurbo = IsToggleModOn(vehicle, 18),
|
||||
modSmokeEnabled = IsToggleModOn(vehicle, 20),
|
||||
modXenon = IsToggleModOn(vehicle, 22),
|
||||
|
||||
modFrontWheels = GetVehicleMod(vehicle, 23),
|
||||
modCustomFrontWheels = GetVehicleModVariation(vehicle, 23),
|
||||
modBackWheels = GetVehicleMod(vehicle, 24),
|
||||
modCustomBackWheels = GetVehicleModVariation(vehicle, 24),
|
||||
|
||||
modPlateHolder = GetVehicleMod(vehicle, 25),
|
||||
modVanityPlate = GetVehicleMod(vehicle, 26),
|
||||
modTrimA = GetVehicleMod(vehicle, 27),
|
||||
modOrnaments = GetVehicleMod(vehicle, 28),
|
||||
modDashboard = GetVehicleMod(vehicle, 29),
|
||||
modDial = GetVehicleMod(vehicle, 30),
|
||||
modDoorSpeaker = GetVehicleMod(vehicle, 31),
|
||||
modSeats = GetVehicleMod(vehicle, 32),
|
||||
modSteeringWheel = GetVehicleMod(vehicle, 33),
|
||||
modShifterLeavers = GetVehicleMod(vehicle, 34),
|
||||
modAPlate = GetVehicleMod(vehicle, 35),
|
||||
modSpeakers = GetVehicleMod(vehicle, 36),
|
||||
modTrunk = GetVehicleMod(vehicle, 37),
|
||||
modHydrolic = GetVehicleMod(vehicle, 38),
|
||||
modEngineBlock = GetVehicleMod(vehicle, 39),
|
||||
modAirFilter = GetVehicleMod(vehicle, 40),
|
||||
modStruts = GetVehicleMod(vehicle, 41),
|
||||
modArchCover = GetVehicleMod(vehicle, 42),
|
||||
modAerials = GetVehicleMod(vehicle, 43),
|
||||
modTrimB = GetVehicleMod(vehicle, 44),
|
||||
modTank = GetVehicleMod(vehicle, 45),
|
||||
modWindows = GetVehicleMod(vehicle, 46),
|
||||
modLivery = GetVehicleMod(vehicle, 48) == -1 and GetVehicleLivery(vehicle) or GetVehicleMod(vehicle, 48),
|
||||
modLightbar = GetVehicleMod(vehicle, 49),
|
||||
}
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to set the properties of
|
||||
---@param props table The properties to set
|
||||
---@return nil
|
||||
function xLib.game.setVehicleProperties(vehicle, props)
|
||||
if not DoesEntityExist(vehicle) then
|
||||
return
|
||||
end
|
||||
local colorPrimary, colorSecondary = GetVehicleColours(vehicle)
|
||||
local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle)
|
||||
SetVehicleModKit(vehicle, 0)
|
||||
|
||||
if props.tyresCanBurst ~= nil then
|
||||
SetVehicleTyresCanBurst(vehicle, props.tyresCanBurst)
|
||||
end
|
||||
|
||||
if props.plate ~= nil then
|
||||
SetVehicleNumberPlateText(vehicle, props.plate)
|
||||
end
|
||||
if props.plateIndex ~= nil then
|
||||
SetVehicleNumberPlateTextIndex(vehicle, props.plateIndex)
|
||||
end
|
||||
if props.bodyHealth ~= nil then
|
||||
SetVehicleBodyHealth(vehicle, props.bodyHealth + 0.0)
|
||||
end
|
||||
if props.engineHealth ~= nil then
|
||||
SetVehicleEngineHealth(vehicle, props.engineHealth + 0.0)
|
||||
end
|
||||
if props.tankHealth ~= nil then
|
||||
SetVehiclePetrolTankHealth(vehicle, props.tankHealth + 0.0)
|
||||
end
|
||||
if props.fuelLevel ~= nil then
|
||||
SetVehicleFuelLevel(vehicle, props.fuelLevel + 0.0)
|
||||
end
|
||||
if props.dirtLevel ~= nil then
|
||||
SetVehicleDirtLevel(vehicle, props.dirtLevel + 0.0)
|
||||
end
|
||||
if props.color1 ~= nil then
|
||||
if type(props.color1) == "table" then
|
||||
SetVehicleCustomPrimaryColour(vehicle, props.color1[1], props.color1[2], props.color1[3])
|
||||
else
|
||||
SetVehicleColours(vehicle, props.color1, colorSecondary)
|
||||
end
|
||||
end
|
||||
if props.color2 ~= nil then
|
||||
if type(props.color2) == "table" then
|
||||
SetVehicleCustomSecondaryColour(vehicle, props.color2[1], props.color2[2], props.color2[3])
|
||||
else
|
||||
SetVehicleColours(vehicle, props.color1 or colorPrimary, props.color2)
|
||||
end
|
||||
end
|
||||
if props.pearlescentColor ~= nil then
|
||||
SetVehicleExtraColours(vehicle, props.pearlescentColor, wheelColor)
|
||||
end
|
||||
|
||||
if props.interiorColor ~= nil then
|
||||
SetVehicleInteriorColor(vehicle, props.interiorColor)
|
||||
end
|
||||
|
||||
if props.dashboardColor ~= nil then
|
||||
SetVehicleDashboardColor(vehicle, props.dashboardColor)
|
||||
end
|
||||
|
||||
if props.wheelColor ~= nil then
|
||||
SetVehicleExtraColours(vehicle, props.pearlescentColor or pearlescentColor, props.wheelColor)
|
||||
end
|
||||
if props.wheels ~= nil then
|
||||
SetVehicleWheelType(vehicle, props.wheels)
|
||||
end
|
||||
if props.windowTint ~= nil then
|
||||
SetVehicleWindowTint(vehicle, props.windowTint)
|
||||
end
|
||||
|
||||
if props.neonEnabled ~= nil then
|
||||
SetVehicleNeonLightEnabled(vehicle, 0, props.neonEnabled[1])
|
||||
SetVehicleNeonLightEnabled(vehicle, 1, props.neonEnabled[2])
|
||||
SetVehicleNeonLightEnabled(vehicle, 2, props.neonEnabled[3])
|
||||
SetVehicleNeonLightEnabled(vehicle, 3, props.neonEnabled[4])
|
||||
end
|
||||
|
||||
if props.extras ~= nil then
|
||||
for extraId, enabled in pairs(props.extras) do
|
||||
extraId = tonumber(extraId)
|
||||
if extraId then
|
||||
SetVehicleExtra(vehicle, extraId, not enabled)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if props.neonColor ~= nil then
|
||||
SetVehicleNeonLightsColour(vehicle, props.neonColor[1], props.neonColor[2], props.neonColor[3])
|
||||
end
|
||||
if props.xenonColor ~= nil then
|
||||
SetVehicleXenonLightsColor(vehicle, props.xenonColor)
|
||||
end
|
||||
if props.customXenonColor ~= nil then
|
||||
SetVehicleXenonLightsCustomColor(vehicle, props.customXenonColor[1], props.customXenonColor[2], props.customXenonColor[3])
|
||||
end
|
||||
if props.modSmokeEnabled ~= nil then
|
||||
ToggleVehicleMod(vehicle, 20, true)
|
||||
end
|
||||
if props.tyreSmokeColor ~= nil then
|
||||
SetVehicleTyreSmokeColor(vehicle, props.tyreSmokeColor[1], props.tyreSmokeColor[2], props.tyreSmokeColor[3])
|
||||
end
|
||||
if props.modSpoilers ~= nil then
|
||||
SetVehicleMod(vehicle, 0, props.modSpoilers, false)
|
||||
end
|
||||
if props.modFrontBumper ~= nil then
|
||||
SetVehicleMod(vehicle, 1, props.modFrontBumper, false)
|
||||
end
|
||||
if props.modRearBumper ~= nil then
|
||||
SetVehicleMod(vehicle, 2, props.modRearBumper, false)
|
||||
end
|
||||
if props.modSideSkirt ~= nil then
|
||||
SetVehicleMod(vehicle, 3, props.modSideSkirt, false)
|
||||
end
|
||||
if props.modExhaust ~= nil then
|
||||
SetVehicleMod(vehicle, 4, props.modExhaust, false)
|
||||
end
|
||||
if props.modFrame ~= nil then
|
||||
SetVehicleMod(vehicle, 5, props.modFrame, false)
|
||||
end
|
||||
if props.modGrille ~= nil then
|
||||
SetVehicleMod(vehicle, 6, props.modGrille, false)
|
||||
end
|
||||
if props.modHood ~= nil then
|
||||
SetVehicleMod(vehicle, 7, props.modHood, false)
|
||||
end
|
||||
if props.modFender ~= nil then
|
||||
SetVehicleMod(vehicle, 8, props.modFender, false)
|
||||
end
|
||||
if props.modRightFender ~= nil then
|
||||
SetVehicleMod(vehicle, 9, props.modRightFender, false)
|
||||
end
|
||||
if props.modRoof ~= nil then
|
||||
SetVehicleMod(vehicle, 10, props.modRoof, false)
|
||||
end
|
||||
|
||||
if props.modRoofLivery ~= nil then
|
||||
SetVehicleRoofLivery(vehicle, props.modRoofLivery)
|
||||
end
|
||||
|
||||
if props.modEngine ~= nil then
|
||||
SetVehicleMod(vehicle, 11, props.modEngine, false)
|
||||
end
|
||||
if props.modBrakes ~= nil then
|
||||
SetVehicleMod(vehicle, 12, props.modBrakes, false)
|
||||
end
|
||||
if props.modTransmission ~= nil then
|
||||
SetVehicleMod(vehicle, 13, props.modTransmission, false)
|
||||
end
|
||||
if props.modHorns ~= nil then
|
||||
SetVehicleMod(vehicle, 14, props.modHorns, false)
|
||||
end
|
||||
if props.modSuspension ~= nil then
|
||||
SetVehicleMod(vehicle, 15, props.modSuspension, false)
|
||||
end
|
||||
if props.modArmor ~= nil then
|
||||
SetVehicleMod(vehicle, 16, props.modArmor, false)
|
||||
end
|
||||
if props.modTurbo ~= nil then
|
||||
ToggleVehicleMod(vehicle, 18, props.modTurbo)
|
||||
end
|
||||
if props.modXenon ~= nil then
|
||||
ToggleVehicleMod(vehicle, 22, props.modXenon)
|
||||
end
|
||||
if props.modFrontWheels ~= nil then
|
||||
SetVehicleMod(vehicle, 23, props.modFrontWheels, props.modCustomFrontWheels)
|
||||
end
|
||||
if props.modBackWheels ~= nil then
|
||||
SetVehicleMod(vehicle, 24, props.modBackWheels, props.modCustomBackWheels)
|
||||
end
|
||||
if props.modPlateHolder ~= nil then
|
||||
SetVehicleMod(vehicle, 25, props.modPlateHolder, false)
|
||||
end
|
||||
if props.modVanityPlate ~= nil then
|
||||
SetVehicleMod(vehicle, 26, props.modVanityPlate, false)
|
||||
end
|
||||
if props.modTrimA ~= nil then
|
||||
SetVehicleMod(vehicle, 27, props.modTrimA, false)
|
||||
end
|
||||
if props.modOrnaments ~= nil then
|
||||
SetVehicleMod(vehicle, 28, props.modOrnaments, false)
|
||||
end
|
||||
if props.modDashboard ~= nil then
|
||||
SetVehicleMod(vehicle, 29, props.modDashboard, false)
|
||||
end
|
||||
if props.modDial ~= nil then
|
||||
SetVehicleMod(vehicle, 30, props.modDial, false)
|
||||
end
|
||||
if props.modDoorSpeaker ~= nil then
|
||||
SetVehicleMod(vehicle, 31, props.modDoorSpeaker, false)
|
||||
end
|
||||
if props.modSeats ~= nil then
|
||||
SetVehicleMod(vehicle, 32, props.modSeats, false)
|
||||
end
|
||||
if props.modSteeringWheel ~= nil then
|
||||
SetVehicleMod(vehicle, 33, props.modSteeringWheel, false)
|
||||
end
|
||||
if props.modShifterLeavers ~= nil then
|
||||
SetVehicleMod(vehicle, 34, props.modShifterLeavers, false)
|
||||
end
|
||||
if props.modAPlate ~= nil then
|
||||
SetVehicleMod(vehicle, 35, props.modAPlate, false)
|
||||
end
|
||||
if props.modSpeakers ~= nil then
|
||||
SetVehicleMod(vehicle, 36, props.modSpeakers, false)
|
||||
end
|
||||
if props.modTrunk ~= nil then
|
||||
SetVehicleMod(vehicle, 37, props.modTrunk, false)
|
||||
end
|
||||
if props.modHydrolic ~= nil then
|
||||
SetVehicleMod(vehicle, 38, props.modHydrolic, false)
|
||||
end
|
||||
if props.modEngineBlock ~= nil then
|
||||
SetVehicleMod(vehicle, 39, props.modEngineBlock, false)
|
||||
end
|
||||
if props.modAirFilter ~= nil then
|
||||
SetVehicleMod(vehicle, 40, props.modAirFilter, false)
|
||||
end
|
||||
if props.modStruts ~= nil then
|
||||
SetVehicleMod(vehicle, 41, props.modStruts, false)
|
||||
end
|
||||
if props.modArchCover ~= nil then
|
||||
SetVehicleMod(vehicle, 42, props.modArchCover, false)
|
||||
end
|
||||
if props.modAerials ~= nil then
|
||||
SetVehicleMod(vehicle, 43, props.modAerials, false)
|
||||
end
|
||||
if props.modTrimB ~= nil then
|
||||
SetVehicleMod(vehicle, 44, props.modTrimB, false)
|
||||
end
|
||||
if props.modTank ~= nil then
|
||||
SetVehicleMod(vehicle, 45, props.modTank, false)
|
||||
end
|
||||
if props.modWindows ~= nil then
|
||||
SetVehicleMod(vehicle, 46, props.modWindows, false)
|
||||
end
|
||||
|
||||
if props.modLivery ~= nil then
|
||||
SetVehicleMod(vehicle, 48, props.modLivery, false)
|
||||
SetVehicleLivery(vehicle, props.modLivery)
|
||||
end
|
||||
|
||||
if props.windowsBroken ~= nil then
|
||||
for k, v in pairs(props.windowsBroken) do
|
||||
if v then
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
RemoveVehicleWindow(vehicle, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if props.doorsBroken ~= nil then
|
||||
for k, v in pairs(props.doorsBroken) do
|
||||
if v then
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
SetVehicleDoorBroken(vehicle, k, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if props.tyreBurst ~= nil then
|
||||
for k, v in pairs(props.tyreBurst) do
|
||||
if v then
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
SetVehicleTyreBurst(vehicle, k, true, 1000.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return xLib.game
|
||||
@@ -0,0 +1,54 @@
|
||||
---@class interactionslib
|
||||
xLib.interactions = {}
|
||||
|
||||
local interactions = {}
|
||||
local pressedInteractions = {}
|
||||
|
||||
---@param name string
|
||||
function xLib.interactions.remove(name)
|
||||
if not interactions[name] then return end
|
||||
interactions[name] = nil
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param onPress function
|
||||
---@param condition? function
|
||||
function xLib.interactions.register(name, onPress, condition)
|
||||
interactions[name] = {
|
||||
condition = condition or function() return true end,
|
||||
onPress = onPress,
|
||||
creator = GetInvokingResource() or "es_extended"
|
||||
}
|
||||
end
|
||||
|
||||
---@return string
|
||||
function xLib.interactions.getInteractKey()
|
||||
local hash = joaat("esx_interact") | 0x80000000
|
||||
return GetControlInstructionalButton(0, hash, true):sub(3)
|
||||
end
|
||||
|
||||
xLib.addKeybind({
|
||||
name = "esx_interact",
|
||||
description = "Interact",
|
||||
defaultMapper = "keyboard",
|
||||
defaultKey = "e",
|
||||
onPressed = function()
|
||||
for _, interaction in pairs(interactions) do
|
||||
local success, result = pcall(interaction.condition)
|
||||
if success and result then
|
||||
pressedInteractions[#pressedInteractions + 1] = interaction
|
||||
interaction.onPress()
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
AddEventHandler("onResourceStop", function(resource)
|
||||
for name, interaction in pairs(interactions) do
|
||||
if interaction.creator == resource then
|
||||
interactions[name] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return xLib.interactions
|
||||
@@ -0,0 +1,164 @@
|
||||
---@class onesynclib
|
||||
xLib.onesync = {}
|
||||
|
||||
---@param source number|vector3
|
||||
---@param closest boolean
|
||||
---@param distance? number
|
||||
---@param ignore? table
|
||||
---@param routingBucket? number
|
||||
local function getNearbyPlayers(source, closest, distance, ignore, routingBucket)
|
||||
local result = {}
|
||||
local count = 0
|
||||
local playerPed
|
||||
local playerCoords
|
||||
ignore = ignore or {}
|
||||
|
||||
if not distance then
|
||||
distance = 100
|
||||
end
|
||||
|
||||
if type(source) == "number" then
|
||||
playerPed = GetPlayerPed(source)
|
||||
|
||||
if not source then
|
||||
error("Received invalid first argument (source); should be playerId")
|
||||
end
|
||||
|
||||
playerCoords = GetEntityCoords(playerPed)
|
||||
|
||||
if not playerCoords then
|
||||
error("Received nil value (playerCoords); perhaps source is nil at first place?")
|
||||
end
|
||||
end
|
||||
|
||||
if type(source) == "vector3" then
|
||||
playerCoords = source
|
||||
|
||||
if not playerCoords then
|
||||
error("Received nil value (playerCoords); perhaps source is nil at first place?")
|
||||
end
|
||||
end
|
||||
|
||||
for _, xPlayer in pairs(ESX.Players) do
|
||||
if not ignore[xPlayer.source] and (not routingBucket or GetPlayerRoutingBucket(xPlayer.source) == routingBucket) then
|
||||
local entity = GetPlayerPed(xPlayer.source)
|
||||
local coords = GetEntityCoords(entity)
|
||||
|
||||
if not closest then
|
||||
local dist = #(playerCoords - coords)
|
||||
if dist <= distance then
|
||||
count = count + 1
|
||||
result[count] = { id = xPlayer.source, ped = NetworkGetNetworkIdFromEntity(entity), coords = coords, dist = dist }
|
||||
end
|
||||
else
|
||||
if xPlayer.source ~= source then
|
||||
local dist = #(playerCoords - coords)
|
||||
if dist <= (result.dist or distance) then
|
||||
result = { id = xPlayer.source, ped = NetworkGetNetworkIdFromEntity(entity), coords = coords, dist = dist }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---@param source vector3|number playerId or vector3 coordinates
|
||||
---@param maxDistance number
|
||||
---@param ignore? table playerIds to ignore, where the key is playerId and value is true
|
||||
---@param routingBucket? number
|
||||
function xLib.onesync.getPlayersInArea(source, maxDistance, ignore, routingBucket)
|
||||
return getNearbyPlayers(source, false, maxDistance, ignore, routingBucket)
|
||||
end
|
||||
|
||||
---@param source vector3|number playerId or vector3 coordinates
|
||||
---@param maxDistance number
|
||||
---@param ignore? table playerIds to ignore, where the key is playerId and value is true
|
||||
---@param routingBucket? number
|
||||
function xLib.onesync.getClosestPlayer(source, maxDistance, ignore, routingBucket)
|
||||
return getNearbyPlayers(source, true, maxDistance, ignore, routingBucket)
|
||||
end
|
||||
|
||||
local function getNearbyEntities(entities, coords, modelFilter, maxDistance, isPed)
|
||||
local nearbyEntities = {}
|
||||
coords = type(coords) == "number" and GetEntityCoords(GetPlayerPed(coords)) or vector3(coords.x, coords.y, coords.z)
|
||||
for _, entity in pairs(entities) do
|
||||
if not isPed or (isPed and not IsPedAPlayer(entity)) then
|
||||
if not modelFilter or modelFilter[GetEntityModel(entity)] then
|
||||
local entityCoords = GetEntityCoords(entity)
|
||||
if not maxDistance or #(coords - entityCoords) <= maxDistance then
|
||||
nearbyEntities[#nearbyEntities + 1] = NetworkGetNetworkIdFromEntity(entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nearbyEntities
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function xLib.onesync.getPedsInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllPeds(), coords, modelFilter, maxDistance, true)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function xLib.onesync.getObjectsInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllObjects(), coords, modelFilter, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table | nil models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function xLib.onesync.getVehiclesInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllVehicles(), coords, modelFilter, maxDistance)
|
||||
end
|
||||
|
||||
local function getClosestEntity(entities, coords, modelFilter, isPed)
|
||||
local distance, closestEntity, closestCoords = 100, 0, vector3(0, 0, 0)
|
||||
coords = type(coords) == "number" and GetEntityCoords(GetPlayerPed(coords)) or vector3(coords.x, coords.y, coords.z)
|
||||
|
||||
for _, entity in pairs(entities) do
|
||||
if not isPed or (isPed and not IsPedAPlayer(entity)) then
|
||||
if not modelFilter or modelFilter[GetEntityModel(entity)] then
|
||||
local entityCoords = GetEntityCoords(entity)
|
||||
local dist = #(coords - entityCoords)
|
||||
if dist < distance then
|
||||
closestEntity, distance, closestCoords = entity, dist, entityCoords
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return NetworkGetNetworkIdFromEntity(closestEntity), distance, closestCoords
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function xLib.onesync.getClosestPed(coords, modelFilter)
|
||||
return getClosestEntity(GetAllPeds(), coords, modelFilter, true)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function xLib.onesync.getClosestObject(coords, modelFilter)
|
||||
return getClosestEntity(GetAllObjects(), coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function xLib.onesync.getClosestVehicle(coords, modelFilter)
|
||||
return getClosestEntity(GetAllVehicles(), coords, modelFilter)
|
||||
end
|
||||
|
||||
return xLib.onesync
|
||||
@@ -0,0 +1,66 @@
|
||||
local overloads <const> = {}
|
||||
|
||||
---@param name string
|
||||
---@param ... unknown
|
||||
local function getOverloadFunction(name, ...)
|
||||
local params = {...}
|
||||
local params_count = #params
|
||||
local is_valid, overload, correct_type
|
||||
|
||||
|
||||
for i=1, #overloads[name] do
|
||||
overload = overloads[name][i]
|
||||
|
||||
if not overload then
|
||||
goto continue
|
||||
end
|
||||
|
||||
is_valid = #overload.valid_types == params_count
|
||||
|
||||
if is_valid then
|
||||
for j = 1, #overload.valid_types do
|
||||
correct_type = overload.valid_types[j]
|
||||
if not xLib.verify(params[j], correct_type) then
|
||||
is_valid = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if is_valid then
|
||||
return overload.cb(...)
|
||||
end
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
error('[xLib] Couldn\'t find a correct method to overload')
|
||||
end
|
||||
|
||||
---Overloads a function
|
||||
---@param name string
|
||||
---@param valid_types CustomType | CustomType[]
|
||||
---@param cb function
|
||||
---@param obj? table
|
||||
function xLib.overload(name, valid_types, cb, obj)
|
||||
xLib.verify(name, {'string'}, true)
|
||||
|
||||
if not overloads[name] then
|
||||
overloads[name] = {}
|
||||
end
|
||||
|
||||
overloads[name][#overloads[name] + 1] = {
|
||||
valid_types = valid_types,
|
||||
cb = cb
|
||||
}
|
||||
|
||||
local env = obj and obj or _ENV
|
||||
|
||||
if not env[name] then
|
||||
env[name] = function(...)
|
||||
return getOverloadFunction(name, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return xLib.overload
|
||||
@@ -0,0 +1,52 @@
|
||||
---@class xPoint
|
||||
---@field coords vector3
|
||||
---@field hidden? boolean
|
||||
---@field enter? function
|
||||
---@field leave? function
|
||||
---@field inside? function
|
||||
---@field handle integer
|
||||
xLib.point = xLib.class()
|
||||
|
||||
---@param properties table coords, distance, hidden, enter, leave, inside
|
||||
function xLib.point:constructor(properties)
|
||||
self.coords = properties.coords
|
||||
self.hidden = properties.hidden
|
||||
self.enter = properties.enter
|
||||
self.leave = properties.leave
|
||||
self.inside = properties.inside
|
||||
|
||||
self.handle = xLib.points.create(
|
||||
properties.coords,
|
||||
properties.distance,
|
||||
properties.hidden,
|
||||
function()
|
||||
if self.enter then
|
||||
self:enter()
|
||||
end
|
||||
end,
|
||||
function()
|
||||
if self.leave then
|
||||
self:leave()
|
||||
end
|
||||
end,
|
||||
properties.inside and function(dist)
|
||||
self:inside(dist)
|
||||
end or nil
|
||||
)
|
||||
end
|
||||
|
||||
function xLib.point:delete()
|
||||
xLib.points.remove(self.handle)
|
||||
end
|
||||
|
||||
---@param hidden? boolean
|
||||
function xLib.point:toggle(hidden)
|
||||
if hidden == nil then
|
||||
hidden = not self.hidden
|
||||
end
|
||||
|
||||
self.hidden = hidden
|
||||
xLib.points.hide(self.handle, hidden)
|
||||
end
|
||||
|
||||
return xLib.point
|
||||
@@ -0,0 +1,100 @@
|
||||
---@class pointslib
|
||||
xLib.points = {}
|
||||
|
||||
local points = {}
|
||||
local insidePoints = {}
|
||||
local handleCount = 0
|
||||
|
||||
---@param coords vector3
|
||||
---@param distance number
|
||||
---@param hidden? boolean
|
||||
---@param enter function
|
||||
---@param leave function
|
||||
---@param inside? function
|
||||
---@return integer handle
|
||||
function xLib.points.create(coords, distance, hidden, enter, leave, inside)
|
||||
handleCount = handleCount + 1
|
||||
local handle = handleCount
|
||||
|
||||
points[handle] = {
|
||||
coords = coords,
|
||||
distance = distance,
|
||||
hidden = hidden,
|
||||
enter = enter,
|
||||
leave = leave,
|
||||
inside = inside,
|
||||
resource = GetInvokingResource()
|
||||
}
|
||||
|
||||
return handle
|
||||
end
|
||||
|
||||
---@param handle integer
|
||||
function xLib.points.remove(handle)
|
||||
points[handle] = nil
|
||||
insidePoints[handle] = nil
|
||||
end
|
||||
|
||||
---@param handle integer
|
||||
---@param hidden boolean
|
||||
function xLib.points.hide(handle, hidden)
|
||||
if points[handle] then
|
||||
points[handle].hidden = hidden
|
||||
end
|
||||
end
|
||||
|
||||
function xLib.points.startLoop()
|
||||
CreateThread(function()
|
||||
local lastScan = 0
|
||||
|
||||
while true do
|
||||
local coords = GetEntityCoords(PlayerPedId())
|
||||
|
||||
for _, point in pairs(insidePoints) do
|
||||
point.inside(#(coords - point.coords))
|
||||
end
|
||||
|
||||
local now = GetGameTimer()
|
||||
if now - lastScan >= 500 then
|
||||
lastScan = now
|
||||
|
||||
for handle, point in pairs(points) do
|
||||
if not point.hidden and #(coords - point.coords) <= point.distance then
|
||||
if not point.nearby then
|
||||
point.nearby = true
|
||||
|
||||
if point.enter then
|
||||
point.enter()
|
||||
end
|
||||
|
||||
if point.inside then
|
||||
insidePoints[handle] = point
|
||||
end
|
||||
end
|
||||
elseif point.nearby then
|
||||
point.nearby = false
|
||||
|
||||
if point.leave then
|
||||
point.leave()
|
||||
end
|
||||
|
||||
insidePoints[handle] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Wait(next(insidePoints) and 0 or 500)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
AddEventHandler("onResourceStop", function(resource)
|
||||
for handle, point in pairs(points) do
|
||||
if point.resource == resource then
|
||||
points[handle] = nil
|
||||
insidePoints[handle] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return xLib.points
|
||||
@@ -0,0 +1,79 @@
|
||||
-- Per-VM client listener for xLib pub/sub. Routes incoming topic data to the
|
||||
-- handlers registered with xLib.pubsub.on. on() only listens; the player must be
|
||||
-- subscribed server-side to actually receive anything.
|
||||
|
||||
local PUBSUB_EVENT <const> = '__xLib_pubsub' -- must match resource/pubsub/server.lua
|
||||
|
||||
-- topic -> array of handlers
|
||||
local handlers = {}
|
||||
|
||||
local pubsub = {}
|
||||
|
||||
---Register a handler for a topic. Multiple handlers per topic are allowed.
|
||||
---@param topic string
|
||||
---@param handler fun(data: any, topic: string)
|
||||
function pubsub.on(topic, handler)
|
||||
if type(topic) ~= 'string' or type(handler) ~= 'function' then
|
||||
return
|
||||
end
|
||||
|
||||
local list = handlers[topic]
|
||||
if not list then
|
||||
list = {}
|
||||
handlers[topic] = list
|
||||
end
|
||||
list[#list + 1] = handler
|
||||
end
|
||||
|
||||
---Remove handlers for a topic. Without a handler argument, removes all of them.
|
||||
---@param topic string
|
||||
---@param handler? function
|
||||
function pubsub.off(topic, handler)
|
||||
if not handler then
|
||||
handlers[topic] = nil
|
||||
return
|
||||
end
|
||||
|
||||
local list = handlers[topic]
|
||||
if not list then
|
||||
return
|
||||
end
|
||||
|
||||
for i = #list, 1, -1 do
|
||||
if list[i] == handler then
|
||||
table.remove(list, i)
|
||||
end
|
||||
end
|
||||
|
||||
if list[1] == nil then
|
||||
handlers[topic] = nil
|
||||
end
|
||||
end
|
||||
|
||||
RegisterNetEvent(PUBSUB_EVENT, function(topic, data)
|
||||
local list = handlers[topic]
|
||||
if not list then
|
||||
return
|
||||
end
|
||||
|
||||
-- fast path: a topic almost always has a single handler, so skip the copy
|
||||
local n = #list
|
||||
if n == 1 then
|
||||
list[1](data, topic)
|
||||
return
|
||||
end
|
||||
|
||||
-- snapshot so a handler that calls off() mid-dispatch cannot shift the list
|
||||
-- and skip a sibling that has not run yet
|
||||
local snapshot = {}
|
||||
for i = 1, n do
|
||||
snapshot[i] = list[i]
|
||||
end
|
||||
|
||||
for i = 1, n do
|
||||
snapshot[i](data, topic)
|
||||
end
|
||||
end)
|
||||
|
||||
xLib.pubsub = pubsub
|
||||
return pubsub
|
||||
@@ -0,0 +1,47 @@
|
||||
-- Per-VM wrapper exposing xLib.pubsub.* on the server; forwards to the shared
|
||||
-- registry (resource/pubsub/server.lua) through its exports.
|
||||
|
||||
local pubsub = {}
|
||||
|
||||
---@param src number serverId of the player to add
|
||||
---@param topic string
|
||||
---@return boolean added
|
||||
function pubsub.subscribe(src, topic)
|
||||
return xLib.pubsub_subscribe(src, topic)
|
||||
end
|
||||
|
||||
---@param src number
|
||||
---@param topic string
|
||||
---@return boolean removed
|
||||
function pubsub.unsubscribe(src, topic)
|
||||
return xLib.pubsub_unsubscribe(src, topic)
|
||||
end
|
||||
|
||||
---@param topic string
|
||||
---@param data any server-trusted data only
|
||||
---@return integer count number of players notified
|
||||
function pubsub.publish(topic, data)
|
||||
return xLib.pubsub_publish(topic, data)
|
||||
end
|
||||
|
||||
---@param topic string
|
||||
---@return number[] serverIds
|
||||
function pubsub.subscribers(topic)
|
||||
return xLib.pubsub_subscribers(topic)
|
||||
end
|
||||
|
||||
---@param src number
|
||||
---@param topic string
|
||||
---@return boolean
|
||||
function pubsub.isSubscribed(src, topic)
|
||||
return xLib.pubsub_isSubscribed(src, topic)
|
||||
end
|
||||
|
||||
---@param topic string
|
||||
---@return integer count
|
||||
function pubsub.clear(topic)
|
||||
return xLib.pubsub_clear(topic)
|
||||
end
|
||||
|
||||
xLib.pubsub = pubsub
|
||||
return pubsub
|
||||
@@ -0,0 +1,77 @@
|
||||
xLib.Raycast = {}
|
||||
xLib.Raycast.__index = xLib.Raycast
|
||||
|
||||
local unpack = table.unpack
|
||||
local GetShapeTestResultIncludingMaterial = GetShapeTestResultIncludingMaterial
|
||||
local StartShapeTestLosProbe = StartShapeTestLosProbe
|
||||
local GetWorldCoordFromScreenCoord = GetWorldCoordFromScreenCoord
|
||||
|
||||
---@param shape integer The shape test handle to wait for
|
||||
---@return boolean, table, table, integer, integer
|
||||
function xLib.Raycast.GetShapeTestResult(shape)
|
||||
local handle, hit, coords, normal, material, entity
|
||||
|
||||
repeat
|
||||
handle, hit, coords, normal, material, entity = GetShapeTestResultIncludingMaterial(shape)
|
||||
Wait(0)
|
||||
until handle ~= 1
|
||||
|
||||
return hit, coords, normal, material, entity
|
||||
end
|
||||
|
||||
---@param depth number The raycast distance
|
||||
---@vararg any Additional arguments to pass to shape test
|
||||
---@return table, boolean, table, table, integer, integer
|
||||
function xLib.Raycast.FromScreen(depth, ...)
|
||||
local worldCoords, normalVector = GetWorldCoordFromScreenCoord(0.5, 0.5)
|
||||
local origin = worldCoords + normalVector
|
||||
local target = worldCoords + normalVector * depth
|
||||
return target, xLib.Raycast.GetShapeTestResult(StartShapeTestLosProbe(origin.x, origin.y, origin.z, target.x, target.y, target.z, ...))
|
||||
end
|
||||
|
||||
-- Start continuous raycasting
|
||||
---@param depth number The raycast distance
|
||||
---@vararg any Additional arguments to pass to shape test
|
||||
function xLib.Raycast.Start(depth, ...)
|
||||
local self = setmetatable({}, xLib.Raycast)
|
||||
|
||||
self.refreshRate = refreshRate
|
||||
self.depth = depth
|
||||
self.args = {...}
|
||||
|
||||
self.active = true
|
||||
|
||||
self._thread = CreateThread(function()
|
||||
while self.active do
|
||||
local target, hit, coords, normal, material, entity = xLib.Raycast.FromScreen(self.depth, unpack(self.args))
|
||||
self.result = {
|
||||
hit = hit,
|
||||
coords = coords,
|
||||
normal = normal,
|
||||
material = material,
|
||||
entity = entity,
|
||||
}
|
||||
Wait(1)
|
||||
end
|
||||
end)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Stop raycasting
|
||||
function xLib.Raycast:Stop()
|
||||
if not self and not self.active then return end
|
||||
self.active = false
|
||||
if self._thread then
|
||||
self._thread = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if the raycating is active
|
||||
---@return boolean
|
||||
function xLib.Raycast:IsActive()
|
||||
if not self and not self.active then return end
|
||||
return self.active
|
||||
end
|
||||
|
||||
return xLib.Raycast
|
||||
@@ -0,0 +1,200 @@
|
||||
--!DISCLAIMER
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
local LOADED <const> = {}
|
||||
local TEMP_DATA <const> = {}
|
||||
|
||||
local _require = require --original lua function
|
||||
|
||||
package = {
|
||||
path = './?.lua;./?/init.lua',
|
||||
preload = {},
|
||||
loaded = setmetatable({}, {
|
||||
__index = LOADED,
|
||||
__newindex = Noop,
|
||||
__metatable = false,
|
||||
})
|
||||
}
|
||||
|
||||
---Gets the resource name and the module's relative path
|
||||
---@param name string -- module name
|
||||
---@return string, string
|
||||
local function getModuleInfo(name)
|
||||
local resource = name:match('^@(.-)/.+') -- if name is for example @esx_lib/imports/require/initl.lua it will capture ESX_LIB
|
||||
|
||||
if resource then
|
||||
return resource, name:sub(#resource + 3) -- returns the path without script name
|
||||
end
|
||||
|
||||
local idx = 4-- call stack depth (kept slightly lower than expected depth "just in case")
|
||||
--- When indexing source of 4 it returns @@esx_lib/imports/require
|
||||
--- When indexing source of 5 it returns for example @@esx_test/client.lua
|
||||
--- Used for identifing resource name.
|
||||
|
||||
while true do
|
||||
local src = debug.getinfo(idx, 'S')?.source
|
||||
|
||||
if not src then
|
||||
error(('Couldn\'t find a source for "%s"'):format(name))
|
||||
end
|
||||
|
||||
resource = src:match('^@@([^/]+)/.+') -- returns resource name
|
||||
|
||||
if resource and not src:find('^@@esx_lib/imports/require') then
|
||||
return resource, name
|
||||
end
|
||||
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
---Searcher for a accessable path
|
||||
---@param name string
|
||||
---@param path string
|
||||
---@return string?, string? -- filename, error message
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function package.searchpath(name, path)
|
||||
local resource, module_name = getModuleInfo(name:gsub('%.', '/'))
|
||||
|
||||
local tried <const> = {}
|
||||
|
||||
for template in path:gmatch('[^;]+') do
|
||||
local file_name = template:gsub('^%./', ''):gsub('?', module_name:gsub('%.', '/') or module_name)
|
||||
|
||||
local file = LoadResourceFile(resource, file_name)
|
||||
|
||||
if file then
|
||||
TEMP_DATA[1] = file
|
||||
TEMP_DATA[2] = resource
|
||||
|
||||
return file_name
|
||||
end
|
||||
|
||||
tried[#tried+1] = ('no file "@%s/%s"'):format(resource, file_name)
|
||||
end
|
||||
|
||||
return nil, table.concat(tried, '\n\t')
|
||||
end
|
||||
|
||||
---Loads module
|
||||
---@param name string
|
||||
---@param env? unknown
|
||||
---@return function?, string?
|
||||
local function loadModule(name, env)
|
||||
local file_name, err = package.searchpath(name, package.path)
|
||||
|
||||
if file_name then
|
||||
local file = TEMP_DATA[1]
|
||||
local resource = TEMP_DATA[2]
|
||||
|
||||
table.wipe(TEMP_DATA)
|
||||
|
||||
return assert(load(file, ('@@%s/%s'):format(resource, file_name), 't', env or _ENV))
|
||||
end
|
||||
|
||||
return nil, err or 'unknown error'
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-doc-alias
|
||||
---@alias PackageSearcher
|
||||
---| fun(name: string): function loader
|
||||
---| fun(name: string): nil|false , string error message
|
||||
---| fun(name: string): function?, string?
|
||||
|
||||
---@type PackageSearcher[]
|
||||
package.searchers = {
|
||||
function (name)
|
||||
local ok, result = pcall(_require, name)
|
||||
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
|
||||
return ok, result
|
||||
end,
|
||||
function (name)
|
||||
if package.preload[name] ~= nil then
|
||||
return package.preload[name]
|
||||
end
|
||||
|
||||
return nil, ('no field package.preload["%s"]'):format(name)
|
||||
end,
|
||||
function(name)
|
||||
return loadModule(name)
|
||||
end,
|
||||
}
|
||||
|
||||
---Loads and runs a Lua file at the given path. Unlike require, the chunk is not cached for future use.
|
||||
---@param file_path string
|
||||
---@param env? unknown
|
||||
function xLib.load(file_path, env)
|
||||
xLib.verify(file_path, 'string', true)
|
||||
|
||||
local result, err = loadModule(file_path, env)
|
||||
|
||||
if result then
|
||||
return result()
|
||||
end
|
||||
|
||||
error(('file "%s" not found\n\t%s'):format(file_path, err))
|
||||
end
|
||||
|
||||
---Loads and decodes a json file at the given path.
|
||||
---@param file_path string
|
||||
function xLib.loadJson(file_path)
|
||||
xLib.verify(file_path, 'string', true)
|
||||
|
||||
local res_source, mod_path = getModuleInfo(file_path:gsub('%.', '/'))
|
||||
|
||||
local res_file = LoadResourceFile(res_source, ('%s.json'):format(mod_path))
|
||||
|
||||
if res_file then
|
||||
return json.decode(res_file)
|
||||
end
|
||||
|
||||
error(('json file "%s" not found\n\tno file "%s/%s.json"'):format(file_path, res_source, mod_path))
|
||||
end
|
||||
|
||||
---Loads the given module, returns any value returned by the seacher (`true` when `nil`).\
|
||||
---Passing `@resourceName.modName` loads a module from a remote resource.
|
||||
---@param name string
|
||||
---@return unknown
|
||||
function xLib.require(name)
|
||||
xLib.verify(name, 'string', true)
|
||||
|
||||
local module = LOADED[name]
|
||||
|
||||
if module == 'loading' then
|
||||
error(('^1circular-dependency occurred when loading module "%s"^0'):format(name))
|
||||
end
|
||||
|
||||
if module ~= nil then return module end
|
||||
|
||||
LOADED[name] = 'loading'
|
||||
|
||||
local err = {}
|
||||
|
||||
for i = 1, #package.searchers do
|
||||
local result, err_msg = package.searchers[i](name)
|
||||
|
||||
if result then
|
||||
if type(result) == 'function' then result = result() end
|
||||
LOADED[name] = result or result == nil
|
||||
|
||||
return LOADED[name]
|
||||
end
|
||||
|
||||
|
||||
err[#err + 1] = err_msg
|
||||
end
|
||||
|
||||
error(('%s'):format(table.concat(err, '\n\t')))
|
||||
end
|
||||
|
||||
return xLib.require
|
||||
+45
-25
@@ -1,22 +1,12 @@
|
||||
ESX.Scaleform = {}
|
||||
ESX.Scaleform.Utils = {}
|
||||
---@class scaleformlib
|
||||
xLib.scaleform = {}
|
||||
xLib.scaleform.utils = {}
|
||||
|
||||
function ESX.Scaleform.ShowFreemodeMessage(title, msg, sec)
|
||||
local scaleform = ESX.Scaleform.Utils.RunMethod("MP_BIG_MESSAGE_FREEMODE", "SHOW_SHARD_WASTED_MP_MESSAGE", false, title, msg)
|
||||
|
||||
local endTime = GetGameTimer() + (sec * 1000)
|
||||
while GetGameTimer() < endTime do
|
||||
Wait(0)
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0)
|
||||
end
|
||||
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
end
|
||||
|
||||
function ESX.Scaleform.ShowBreakingNews(title, msg, bottom, sec)
|
||||
local scaleform = ESX.Scaleform.Utils.RunMethod("BREAKING_NEWS", "SET_TEXT", false, msg, bottom)
|
||||
ESX.Scaleform.Utils.RunMethod(scaleform, "SET_SCROLL_TEXT", false, 0, 0, title)
|
||||
ESX.Scaleform.Utils.RunMethod(scaleform, "DISPLAY_SCROLL_TEXT", false, 0, 0)
|
||||
---@param title string
|
||||
---@param msg string
|
||||
---@param sec number
|
||||
function xLib.scaleform.showFreemodeMessage(title, msg, sec)
|
||||
local scaleform = xLib.scaleform.utils.runMethod("MP_BIG_MESSAGE_FREEMODE", "SHOW_SHARD_WASTED_MP_MESSAGE", false, title, msg)
|
||||
|
||||
local endTime = GetGameTimer() + (sec * 1000)
|
||||
while GetGameTimer() < endTime do
|
||||
@@ -27,8 +17,14 @@ function ESX.Scaleform.ShowBreakingNews(title, msg, bottom, sec)
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
end
|
||||
|
||||
function ESX.Scaleform.ShowPopupWarning(title, msg, bottom, sec)
|
||||
local scaleform = ESX.Scaleform.Utils.RunMethod("POPUP_WARNING", "SHOW_POPUP_WARNING", false, 500.0, title, msg, bottom, true)
|
||||
---@param title string
|
||||
---@param msg string
|
||||
---@param bottom string
|
||||
---@param sec number
|
||||
function xLib.scaleform.showBreakingNews(title, msg, bottom, sec)
|
||||
local scaleform = xLib.scaleform.utils.runMethod("BREAKING_NEWS", "SET_TEXT", false, msg, bottom)
|
||||
xLib.scaleform.utils.runMethod(scaleform, "SET_SCROLL_TEXT", false, 0, 0, title)
|
||||
xLib.scaleform.utils.runMethod(scaleform, "DISPLAY_SCROLL_TEXT", false, 0, 0)
|
||||
|
||||
local endTime = GetGameTimer() + (sec * 1000)
|
||||
while GetGameTimer() < endTime do
|
||||
@@ -39,8 +35,12 @@ function ESX.Scaleform.ShowPopupWarning(title, msg, bottom, sec)
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
end
|
||||
|
||||
function ESX.Scaleform.ShowTrafficMovie(sec)
|
||||
local scaleform = ESX.Scaleform.Utils.RunMethod("TRAFFIC_CAM", "PLAY_CAM_MOVIE", false)
|
||||
---@param title string
|
||||
---@param msg string
|
||||
---@param bottom string
|
||||
---@param sec number
|
||||
function xLib.scaleform.showPopupWarning(title, msg, bottom, sec)
|
||||
local scaleform = xLib.scaleform.utils.runMethod("POPUP_WARNING", "SHOW_POPUP_WARNING", false, 500.0, title, msg, bottom, true)
|
||||
|
||||
local endTime = GetGameTimer() + (sec * 1000)
|
||||
while GetGameTimer() < endTime do
|
||||
@@ -51,7 +51,22 @@ function ESX.Scaleform.ShowTrafficMovie(sec)
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
end
|
||||
|
||||
function ESX.Scaleform.Utils.RequestScaleformMovie(movie)
|
||||
---@param sec number
|
||||
function xLib.scaleform.showTrafficMovie(sec)
|
||||
local scaleform = xLib.scaleform.utils.runMethod("TRAFFIC_CAM", "PLAY_CAM_MOVIE", false)
|
||||
|
||||
local endTime = GetGameTimer() + (sec * 1000)
|
||||
while GetGameTimer() < endTime do
|
||||
Wait(0)
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0)
|
||||
end
|
||||
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
end
|
||||
|
||||
---@param movie string
|
||||
---@return number
|
||||
function xLib.scaleform.utils.requestScaleformMovie(movie)
|
||||
local scaleform = RequestScaleformMovie(movie)
|
||||
|
||||
while not HasScaleformMovieLoaded(scaleform) do
|
||||
@@ -68,8 +83,11 @@ end
|
||||
---@param returnValue? boolean # Whether to return the value from the method
|
||||
---@param ... number|string|boolean # Arguments to pass to the method
|
||||
---@return number, number? # The scaleform handle, and the return value if `returnValue` is true
|
||||
function ESX.Scaleform.Utils.RunMethod(scaleform, methodName, returnValue, ...)
|
||||
scaleform = type(scaleform) == "number" and scaleform or ESX.Scaleform.Utils.RequestScaleformMovie(scaleform)
|
||||
function xLib.scaleform.utils.runMethod(scaleform, methodName, returnValue, ...)
|
||||
if type(scaleform) ~= "number" then
|
||||
scaleform = xLib.scaleform.utils.requestScaleformMovie(scaleform)
|
||||
end
|
||||
|
||||
BeginScaleformMovieMethod(scaleform, methodName)
|
||||
|
||||
local args = { ... }
|
||||
@@ -97,3 +115,5 @@ function ESX.Scaleform.Utils.RunMethod(scaleform, methodName, returnValue, ...)
|
||||
|
||||
return scaleform
|
||||
end
|
||||
|
||||
return xLib.scaleform
|
||||
+22
-7
@@ -1,9 +1,10 @@
|
||||
ESX.Streaming = {}
|
||||
---@class streaminglib
|
||||
xLib.streaming = {}
|
||||
|
||||
---@param modelHash number | string
|
||||
---@param cb? function
|
||||
---@return number | nil
|
||||
function ESX.Streaming.RequestModel(modelHash, cb)
|
||||
xLib.streaming.requestModel = function(modelHash, cb)
|
||||
modelHash = type(modelHash) == "number" and modelHash or joaat(modelHash)
|
||||
|
||||
if not IsModelInCdimage(modelHash) then return end
|
||||
@@ -17,7 +18,7 @@ end
|
||||
---@param textureDict string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestStreamedTextureDict(textureDict, cb)
|
||||
xLib.streaming.requestStreamedTextureDict = function(textureDict, cb)
|
||||
RequestStreamedTextureDict(textureDict, false)
|
||||
|
||||
while not HasStreamedTextureDictLoaded(textureDict) do Wait(500) end
|
||||
@@ -28,7 +29,7 @@ end
|
||||
---@param assetName string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestNamedPtfxAsset(assetName, cb)
|
||||
xLib.streaming.requestNamedPtfxAsset = function(assetName, cb)
|
||||
RequestNamedPtfxAsset(assetName)
|
||||
|
||||
while not HasNamedPtfxAssetLoaded(assetName) do Wait(500) end
|
||||
@@ -39,7 +40,7 @@ end
|
||||
---@param animSet string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestAnimSet(animSet, cb)
|
||||
xLib.streaming.requestAnimSet = function(animSet, cb)
|
||||
RequestAnimSet(animSet)
|
||||
|
||||
while not HasAnimSetLoaded(animSet) do Wait(500) end
|
||||
@@ -50,7 +51,7 @@ end
|
||||
---@param animDict string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestAnimDict(animDict, cb)
|
||||
xLib.streaming.requestAnimDict = function(animDict, cb)
|
||||
RequestAnimDict(animDict)
|
||||
|
||||
while not HasAnimDictLoaded(animDict) do Wait(500) end
|
||||
@@ -61,10 +62,24 @@ end
|
||||
---@param weaponHash number | string
|
||||
---@param cb? function
|
||||
---@return string | number | nil
|
||||
function ESX.Streaming.RequestWeaponAsset(weaponHash, cb)
|
||||
xLib.streaming.requestWeaponAsset = function(weaponHash, cb)
|
||||
RequestWeaponAsset(weaponHash, 31, 0)
|
||||
|
||||
while not HasWeaponAssetLoaded(weaponHash) do Wait(500) end
|
||||
|
||||
return cb and cb(weaponHash) or weaponHash
|
||||
end
|
||||
|
||||
---@param bankName string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
xLib.streaming.requestAudioBank = function(bankName, cb)
|
||||
RequestAudioBank(bankName, false)
|
||||
|
||||
while not RequestScriptAudioBank(bankName, false) do Wait(500) end
|
||||
|
||||
return cb and cb(bankName) or bankName
|
||||
end
|
||||
|
||||
|
||||
return xLib.streaming
|
||||
@@ -0,0 +1,147 @@
|
||||
---@class stringlib
|
||||
xLib.string = string
|
||||
|
||||
--- Normalize (trim + lowercase)
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.normalize(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:match("^%s*(.-)%s*$"):lower()
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.capitalize(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:gsub("^%l", string.upper)
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.toSnake(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:gsub("%s+", "_"):gsub("([a-z%d])([A-Z])", "%1_%2"):lower()
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.toCamel(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:lower():gsub("_%a", function(w) return w:sub(2):upper() end)
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.toPascal(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:gsub("(%a)([%w_]*)", function(first, rest)
|
||||
return first:upper() .. rest:lower()
|
||||
end):gsub("_", "")
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@return string
|
||||
function xLib.string.escapePattern(s)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local res = s:gsub("([%%%^%$%(%)%.%[%]%*%+%-%?])", "%%%1")
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@return boolean
|
||||
function xLib.string.matchSafe(s, pattern)
|
||||
local ok, result = pcall(string.match, s, pattern)
|
||||
return ok and result ~= nil
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@return string
|
||||
function xLib.string.before(s, pattern)
|
||||
xLib.verify(s, 'string', true)
|
||||
local start = s:find(pattern, 1, true)
|
||||
|
||||
if not start then return s end
|
||||
|
||||
return s:sub(1, start - 1)
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@return string
|
||||
function xLib.string.after(s, pattern)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local _, finish = s:find(pattern, 1, true)
|
||||
|
||||
if not finish then return s end
|
||||
|
||||
return s:sub(finish + 1)
|
||||
end
|
||||
|
||||
|
||||
---@param s string
|
||||
---@param substr string
|
||||
---@return boolean
|
||||
function xLib.string.contains(s, substr)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
return s:find(substr, 1, true) ~= nil
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param old string
|
||||
---@param new string
|
||||
---@return string
|
||||
function xLib.string.replace(s, old, new)
|
||||
xLib.verify(s, 'string', true)
|
||||
|
||||
local result = s:gsub(xLib.string.escapePattern(old), new)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
---@param length number
|
||||
---@return string
|
||||
function xLib.string.randomHex(length)
|
||||
local t = {}
|
||||
|
||||
for _ = 1, length do
|
||||
t[#t + 1] = string.format("%x", math.random(0, 15))
|
||||
end
|
||||
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
---@return string
|
||||
function xLib.string.uuid()
|
||||
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
|
||||
|
||||
local uuid = template:gsub("[xy]", function(c)
|
||||
local v = (c == "x") and math.random(0, 15) or math.random(8, 11)
|
||||
return string.format("%x", v)
|
||||
end)
|
||||
|
||||
return uuid
|
||||
end
|
||||
|
||||
return xLib.string
|
||||
@@ -0,0 +1,359 @@
|
||||
---@class xtable : tablelib
|
||||
xLib.table = setmetatable({}, { __index = table })
|
||||
|
||||
---@param tbl table
|
||||
---@return boolean
|
||||
function xLib.table.isArray(tbl)
|
||||
xLib.verify(tbl, "table", true)
|
||||
|
||||
local count, maxIndex = 0, 0
|
||||
|
||||
for k, _ in pairs(tbl) do
|
||||
if type(k) ~= "number" or k < 1 or k % 1 ~= 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
count, maxIndex = count + 1, math.max(maxIndex, k)
|
||||
|
||||
if count > maxIndex then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param item any
|
||||
---@return any
|
||||
function xLib.table.searchForKey(tbl, item)
|
||||
xLib.verify(tbl, "table", true)
|
||||
|
||||
for k, v in pairs(tbl) do
|
||||
if v == item then
|
||||
return k
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param item any
|
||||
---@return boolean
|
||||
function xLib.table.contains(tbl, item)
|
||||
return xLib.table.searchForKey(tbl, item) ~= nil
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param filter fun(value:any, key:any):boolean
|
||||
---@return table
|
||||
function xLib.table.filter(tbl, filter)
|
||||
xLib.verify(tbl, "table", true)
|
||||
xLib.verify(filter, "function", true)
|
||||
|
||||
local result = {}
|
||||
|
||||
for k, v in pairs(tbl) do
|
||||
if filter(v, k) then
|
||||
result[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param copies? table
|
||||
---@return table
|
||||
function xLib.table.deepcopy(tbl, copies)
|
||||
xLib.verify(tbl, "table", true)
|
||||
|
||||
copies = copies or {}
|
||||
|
||||
if copies[tbl] then
|
||||
return copies[tbl]
|
||||
end
|
||||
|
||||
local copy = {}
|
||||
copies[tbl] = copy
|
||||
|
||||
for k, v in pairs(tbl) do
|
||||
copy[k] = type(v) == "table" and xLib.table.deepcopy(v, copies) or v
|
||||
|
||||
if type(v) == "table" and getmetatable(v) then
|
||||
setmetatable(copy[k], getmetatable(v))
|
||||
end
|
||||
end
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
--- https://github.com/overextended/ox_lib/blob/master/imports/table/shared.lua
|
||||
---@param t1 table
|
||||
---@param t2 table
|
||||
---@param addDuplicateNumbers boolean
|
||||
---@return table
|
||||
function xLib.table.merge(t1, t2, addDuplicateNumbers)
|
||||
xLib.verify(t1, "table", true)
|
||||
xLib.verify(t2, "table", true)
|
||||
xLib.verify(addDuplicateNumbers, "boolean", true)
|
||||
|
||||
addDuplicateNumbers = addDuplicateNumbers == nil or addDuplicateNumbers
|
||||
for k, v2 in pairs(t2) do
|
||||
local v1 = t1[k]
|
||||
local type1 = type(v1)
|
||||
local type2 = type(v2)
|
||||
|
||||
if type1 == 'table' and type2 == 'table' then
|
||||
xLib.table.merge(v1, v2, addDuplicateNumbers)
|
||||
elseif addDuplicateNumbers and (type1 == 'number' and type2 == 'number') then
|
||||
t1[k] = v1 + v2
|
||||
else
|
||||
t1[k] = v2
|
||||
end
|
||||
end
|
||||
|
||||
return t1
|
||||
end
|
||||
|
||||
function xLib.table.dump(tbl)
|
||||
if xLib.verify(tbl, 'table') then
|
||||
local s = '{ '
|
||||
for k,v in pairs(tbl) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. tbl(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(tbl)
|
||||
end
|
||||
end
|
||||
|
||||
-- nil proof alternative to #table
|
||||
---@param t table
|
||||
---@return number
|
||||
function xLib.table.sizeOf(t)
|
||||
local count = 0
|
||||
|
||||
for _, _ in pairs(t) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function xLib.table.set(t)
|
||||
local set = {}
|
||||
for _, v in ipairs(t) do
|
||||
set[v] = true
|
||||
end
|
||||
return set
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param value any
|
||||
---@return number
|
||||
function xLib.table.indexOf(t, value)
|
||||
for i = 1, #t, 1 do
|
||||
if t[i] == value then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param value any
|
||||
---@return number
|
||||
function xLib.table.lastIndexOf(t, value)
|
||||
for i = #t, 1, -1 do
|
||||
if t[i] == value then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return any
|
||||
function xLib.table.find(t, cb)
|
||||
for i = 1, #t, 1 do
|
||||
if cb(t[i]) then
|
||||
return t[i]
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return number
|
||||
function xLib.table.findIndex(t, cb)
|
||||
for i = 1, #t, 1 do
|
||||
if cb(t[i]) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return table
|
||||
function xLib.table.map(t, cb)
|
||||
local newTable = {}
|
||||
|
||||
for i = 1, #t, 1 do
|
||||
newTable[i] = cb(t[i], i)
|
||||
end
|
||||
|
||||
return newTable
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function xLib.table.reverse(t)
|
||||
local newTable = {}
|
||||
|
||||
for i = #t, 1, -1 do
|
||||
table.insert(newTable, t[i])
|
||||
end
|
||||
|
||||
return newTable
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function xLib.table.clone(t)
|
||||
if type(t) ~= "table" then
|
||||
return t
|
||||
end
|
||||
|
||||
local meta = getmetatable(t)
|
||||
local target = {}
|
||||
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == "table" then
|
||||
target[k] = xLib.table.clone(v)
|
||||
else
|
||||
target[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(target, meta)
|
||||
|
||||
return target
|
||||
end
|
||||
|
||||
---@param t1 table
|
||||
---@param t2 table
|
||||
---@return table
|
||||
function xLib.table.concat(t1, t2)
|
||||
local t3 = xLib.table.clone(t1)
|
||||
|
||||
for i = 1, #t2, 1 do
|
||||
table.insert(t3, t2[i])
|
||||
end
|
||||
|
||||
return t3
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param sep string
|
||||
---@return string
|
||||
function xLib.table.join(t, sep)
|
||||
local str = ""
|
||||
|
||||
for i = 1, #t, 1 do
|
||||
if i > 1 then
|
||||
str = str .. (sep or ",")
|
||||
end
|
||||
|
||||
str = str .. t[i]
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
-- Credits: https://github.com/JonasDev99/qb-garages/blob/b0335d67cb72a6b9ac60f62a87fb3946f5c2f33d/server/main.lua#L5
|
||||
---@param tab table
|
||||
---@param val any
|
||||
---@return boolean
|
||||
function xLib.table.contains(tab, val)
|
||||
if type(val) == "table" then
|
||||
for _, value in pairs(tab) do
|
||||
if xLib.table.contains(val, value) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
else
|
||||
for _, value in pairs(tab) do
|
||||
if value == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Credit: https://stackoverflow.com/a/15706820
|
||||
-- Description: sort function for pairs
|
||||
---@param t table
|
||||
---@param order function
|
||||
---@return function
|
||||
function xLib.table.sort(t, order)
|
||||
-- collect the keys
|
||||
local keys = {}
|
||||
|
||||
for k, _ in pairs(t) do
|
||||
keys[#keys + 1] = k
|
||||
end
|
||||
|
||||
-- if order function given, sort by it by passing the table and keys a, b,
|
||||
-- otherwise just sort the keys
|
||||
if order then
|
||||
table.sort(keys, function(a, b)
|
||||
return order(t, a, b)
|
||||
end)
|
||||
else
|
||||
table.sort(keys)
|
||||
end
|
||||
|
||||
-- return the iterator function
|
||||
local i = 0
|
||||
|
||||
return function()
|
||||
i = i + 1
|
||||
if keys[i] then
|
||||
return keys[i], t[keys[i]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return Array
|
||||
function xLib.table.toArray(t)
|
||||
local array = {}
|
||||
for _, v in pairs(t) do
|
||||
array[#array + 1] = v
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function xLib.table.wipe(t)
|
||||
return table.wipe(t)
|
||||
end
|
||||
|
||||
|
||||
return xLib.table
|
||||
+8
-3
@@ -1,10 +1,14 @@
|
||||
xLib.timeout = {}
|
||||
|
||||
local TimeoutCount = 0
|
||||
local CancelledTimeouts = {}
|
||||
|
||||
---@param msec number
|
||||
---@param cb function
|
||||
---@return number
|
||||
ESX.SetTimeout = function(msec, cb)
|
||||
xLib.timeout.setTimeout = function(msec, cb)
|
||||
xLib.verify(cb, "function", true)
|
||||
|
||||
local id <const> = TimeoutCount + 1
|
||||
|
||||
SetTimeout(msec, function()
|
||||
@@ -12,7 +16,6 @@ ESX.SetTimeout = function(msec, cb)
|
||||
CancelledTimeouts[id] = nil
|
||||
return
|
||||
end
|
||||
|
||||
cb()
|
||||
end)
|
||||
|
||||
@@ -23,6 +26,8 @@ end
|
||||
|
||||
---@param id number
|
||||
---@return nil
|
||||
ESX.ClearTimeout = function(id)
|
||||
xLib.timeout.clearTimeout = function(id)
|
||||
CancelledTimeouts[id] = true
|
||||
end
|
||||
|
||||
return xLib.timeout
|
||||
@@ -0,0 +1,161 @@
|
||||
---@alias CustomType 'number' | 'boolean' | 'function' | 'table' | 'string' | 'nil' | 'array' | 'int' | 'uint' | 'float' | 'char' | 'vector3' | 'vector4' | 'ped' | 'playerId' | 'vehicle' | 'prop' | 'class' | 'model'
|
||||
|
||||
---Checks if value is an array
|
||||
---@param value any
|
||||
---@return boolean
|
||||
local function isArray(value)
|
||||
if type(value) ~= 'table' then
|
||||
return false
|
||||
end
|
||||
|
||||
local i = 0
|
||||
|
||||
for _ in pairs(value) do
|
||||
i = i + 1
|
||||
if value[i] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---Checks if player id is correct.
|
||||
---@param value any
|
||||
---@return boolean
|
||||
local function isPlayerId(value)
|
||||
if type(value) ~= 'number' then
|
||||
return false
|
||||
end
|
||||
|
||||
if xLib.side == 'server' then
|
||||
return GetPlayerName(value) ~= nil
|
||||
else
|
||||
return NetworkIsPlayerActive(value)
|
||||
end
|
||||
end
|
||||
|
||||
---Checks if function is callable.
|
||||
---@param value any
|
||||
---@return boolean
|
||||
local function isCallable(value)
|
||||
local value_type = type(value)
|
||||
|
||||
if value_type == 'function' then
|
||||
return true
|
||||
end
|
||||
|
||||
if value_type == 'table' then
|
||||
local mt = getmetatable(value)
|
||||
|
||||
if mt and mt.__call then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
xLib.callback.register('xLib:validateModel', function(model)
|
||||
return IsModelValid(model)
|
||||
end)
|
||||
|
||||
local function validateModel(model)
|
||||
local players = GetPlayers()
|
||||
local source = tonumber(players[math.random(1,#players)])
|
||||
|
||||
return xLib.callback.await('xLib:validateModel', source, false, model)
|
||||
end
|
||||
|
||||
---Make sure value is a valid type
|
||||
---@param value any
|
||||
---@param valid_type CustomType
|
||||
---@return boolean
|
||||
local function verifyType(value, valid_type)
|
||||
if valid_type == 'function' then
|
||||
return isCallable(value)
|
||||
elseif valid_type == 'array' then
|
||||
return isArray(value)
|
||||
elseif valid_type == 'int' then
|
||||
return math.type(value) == 'integer'
|
||||
elseif valid_type == 'float' then
|
||||
return math.type(value) == 'float'
|
||||
elseif valid_type == 'uint' then
|
||||
return math.type(value) == 'int' and value >= 0
|
||||
elseif valid_type == 'char' then
|
||||
return type(value) == 'string' and #value == 1
|
||||
elseif valid_type == 'ped' then
|
||||
return GetEntityType(value) == 1
|
||||
elseif valid_type == 'vehicle' then
|
||||
return GetEntityType(value) == 2
|
||||
elseif valid_type == 'prop' then
|
||||
return GetEntityType(value) == 3
|
||||
elseif valid_type == 'playerId' then
|
||||
return isPlayerId(value)
|
||||
elseif valid_type == 'class' then
|
||||
if type(value) ~= 'table' then
|
||||
return false
|
||||
end
|
||||
|
||||
return getmetatable(value)?._isClass == true
|
||||
elseif valid_type == 'model' then
|
||||
if xLib.side == "server" then
|
||||
return validateModel(value)
|
||||
end
|
||||
|
||||
return IsModelValid(value)
|
||||
end
|
||||
|
||||
return type(value) == valid_type
|
||||
end
|
||||
|
||||
---@class VerifyTable
|
||||
---@field debug VerifyFunction
|
||||
|
||||
---@alias VerifyFunction fun(value: any, valid_types: CustomType[] | CustomType, throw_error?: boolean): boolean
|
||||
|
||||
---@type VerifyFunction | VerifyTable
|
||||
xLib.verify = setmetatable({
|
||||
fn = function(value, valid_types, throw_error)
|
||||
local match = false
|
||||
|
||||
if type(valid_types) == 'table' then
|
||||
for i = 1, #valid_types do
|
||||
if xLib.verify(value, valid_types[i]) then
|
||||
match = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if throw_error and not match then
|
||||
error(('[xLib] Couldn\'t match %s to types %s'):format(value, json.encode(valid_types)))
|
||||
else
|
||||
return match
|
||||
end
|
||||
end
|
||||
|
||||
match = verifyType(value, valid_types)
|
||||
|
||||
if throw_error and not match then
|
||||
error(('[xLib] Couldn\'t match %s to type %s'):format(value, valid_types))
|
||||
else
|
||||
return match
|
||||
end
|
||||
end
|
||||
}, {
|
||||
__index = function(self, k)
|
||||
if k == 'debug' and xLib.debug then
|
||||
return rawget(self, 'fn'
|
||||
)
|
||||
elseif k ~= 'debug' then
|
||||
return rawget(self, k)
|
||||
else
|
||||
return Noop
|
||||
end
|
||||
end,
|
||||
__call = function(self, ...)
|
||||
return rawget(self, 'fn')(...)
|
||||
end
|
||||
})
|
||||
|
||||
return xLib.verify
|
||||
@@ -0,0 +1,45 @@
|
||||
---Yields the current thread until the callback returns a non-nil value.
|
||||
---@generic T
|
||||
---@param cb fun(): T?
|
||||
---@param errMessage? string
|
||||
---@param timeout? number | false Error out after `~x` ms if the callback hasn't resolved. Defaults to 1000, unless set to `false`.
|
||||
---@param interval? integer Polling interval in ms. Defaults to 0. A value above `timeout` overshoots it by one cycle.
|
||||
---@return T
|
||||
---@async
|
||||
function xLib.waitFor(cb, errMessage, timeout, interval)
|
||||
xLib.verify(cb, 'function', true)
|
||||
|
||||
if interval then
|
||||
xLib.verify(interval, 'int', true)
|
||||
end
|
||||
|
||||
local value = cb()
|
||||
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
|
||||
if timeout ~= false and type(timeout) ~= 'number' then
|
||||
timeout = 5000
|
||||
end
|
||||
|
||||
local start = timeout and GetGameTimer()
|
||||
|
||||
while value == nil do
|
||||
Wait(interval or 0)
|
||||
|
||||
value = cb()
|
||||
|
||||
if value == nil and timeout then
|
||||
local elapsed = GetGameTimer() - start
|
||||
|
||||
if elapsed > timeout then
|
||||
return error(('%s (waited %.1fms)'):format(errMessage or 'failed to resolve callback', elapsed), 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
return xLib.waitFor
|
||||
@@ -0,0 +1,74 @@
|
||||
--[[
|
||||
https://github.com/overextended/ox_lib
|
||||
|
||||
This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
|
||||
|
||||
Copyright © 2025 Linden <https://github.com/thelindat>
|
||||
]]
|
||||
|
||||
local registeredCallbacks = {}
|
||||
local resource_name = GetCurrentResourceName() --TODO: Add cache
|
||||
local IS_DEBUG <const> = GetConvar("xLib:debug", "false") == "true"
|
||||
|
||||
|
||||
AddEventHandler('onResourceStop', function(resourceName)
|
||||
if resource_name == resourceName then return end
|
||||
|
||||
for callbackName, resource in pairs(registeredCallbacks) do
|
||||
if resource == resourceName then
|
||||
registeredCallbacks[callbackName] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
---For internal use only.
|
||||
---Sets a callback event as registered to a specific resource, preventing it from
|
||||
---being overwritten. Any unknown callbacks will return an error to the caller.
|
||||
---@param callbackName string
|
||||
---@param isValid boolean
|
||||
function xLib.setValidCallback(callbackName, isValid)
|
||||
local resourceName = GetInvokingResource() or resource_name
|
||||
local callbackResource = registeredCallbacks[callbackName]
|
||||
|
||||
if callbackResource then
|
||||
if not isValid then
|
||||
callbackResource[callbackName] = nil
|
||||
return
|
||||
end
|
||||
|
||||
if callbackResource == resourceName then return end
|
||||
|
||||
if IS_DEBUG then
|
||||
local errMessage = ("^1resource '%s' attempted to overwrite callback '%s' owned by resource '%s'^0"):format(resourceName, callbackName, callbackResource)
|
||||
|
||||
print(('^1SCRIPT ERROR: %s^0\n%s'):format(errMessage,
|
||||
Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()) or ''))
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if IS_DEBUG then
|
||||
print(("set valid callback '%s' for resource '%s'"):format(callbackName, resourceName))
|
||||
end
|
||||
|
||||
registeredCallbacks[callbackName] = resourceName
|
||||
end
|
||||
|
||||
function xLib.isCallbackValid(callbackName)
|
||||
return registeredCallbacks[callbackName] == GetInvokingResource() or resource_name
|
||||
end
|
||||
|
||||
local cbEvent = '__xLib_cb_%s'
|
||||
|
||||
RegisterNetEvent('xLib:validateCallback', function(callbackName, invokingResource, key)
|
||||
if registeredCallbacks[callbackName] then return end
|
||||
|
||||
local event = cbEvent:format(invokingResource)
|
||||
|
||||
if GetGameName() == 'fxserver' then
|
||||
return TriggerClientEvent(event, source, key, 'cb_invalid')
|
||||
end
|
||||
|
||||
TriggerServerEvent(event, key, 'cb_invalid')
|
||||
end)
|
||||
@@ -0,0 +1,38 @@
|
||||
---@diagnostic disable: lowercase-global
|
||||
xLib = setmetatable({
|
||||
name = 'xLib',
|
||||
side = IsDuplicityVersion() and 'server' or 'client'
|
||||
}, {
|
||||
__newindex = function(self, key, fn)
|
||||
rawset(self, key, fn)
|
||||
|
||||
if debug.getinfo(2, 'S').short_src:find('@esx_lib/resource') then
|
||||
exports(key, fn)
|
||||
end
|
||||
end,
|
||||
|
||||
__index = function(self, key)
|
||||
local dir = ('imports/%s'):format(key)
|
||||
local chunk = LoadResourceFile(self.name, ('%s/%s.lua'):format(dir, self.side))
|
||||
|
||||
local shared = LoadResourceFile(self.name, ('%s/shared.lua'):format(dir))
|
||||
|
||||
if shared then
|
||||
chunk = (chunk and ('%s\n%s'):format(shared, chunk)) or shared
|
||||
end
|
||||
|
||||
if chunk then
|
||||
local fn, err = load(chunk, ('@@esx_lib/%s/%s.lua'):format(key, self.side))
|
||||
|
||||
if not fn or err then
|
||||
return error(('\n^1Error importing module (%s): %s^0'):format(dir, err), 3)
|
||||
end
|
||||
|
||||
rawset(self, key, fn() or Noop)
|
||||
|
||||
return self[key]
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
require = xLib.require
|
||||
@@ -0,0 +1,207 @@
|
||||
-- xLib pub/sub: a producer subscribes players to a topic server-side and
|
||||
-- publishes data pushed only to them. Clients only listen, never subscribe.
|
||||
-- The registry is shared here (esx_lib server VM). Namespace topics by resource
|
||||
-- (e.g. "esx_shops:247:stock") so two resources cannot clash on a bare name.
|
||||
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
|
||||
local PUBSUB_EVENT <const> = '__xLib_pubsub' -- must match imports/pubsub/client.lua
|
||||
local TOPIC_PATTERN <const> = '^[%w_:%.%-]+$'
|
||||
local MAX_TOPIC_LEN <const> = 200
|
||||
local resourceName <const> = GetCurrentResourceName()
|
||||
|
||||
-- topic -> set of subscribed serverIds: subs[topic][src] = true
|
||||
local subs = {}
|
||||
-- serverId -> set of topics: memberOf[src][topic] = true (O(degree) cleanup on drop)
|
||||
local memberOf = {}
|
||||
-- topic -> resource that created it, purged on that resource's onResourceStop
|
||||
local ownerOf = {}
|
||||
|
||||
local function isValidTopic(topic)
|
||||
return type(topic) == 'string'
|
||||
and #topic > 0
|
||||
and #topic <= MAX_TOPIC_LEN
|
||||
and topic:match(TOPIC_PATTERN) ~= nil
|
||||
end
|
||||
|
||||
local function isLivePlayer(src)
|
||||
-- a connected player has a name; rejects 0, stale or recycled serverIds
|
||||
return type(src) == 'number' and src > 0 and GetPlayerName(src) ~= nil
|
||||
end
|
||||
|
||||
local function detach(src, topic)
|
||||
local set = subs[topic]
|
||||
if set then
|
||||
set[src] = nil
|
||||
if next(set) == nil then
|
||||
subs[topic] = nil
|
||||
ownerOf[topic] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local topics = memberOf[src]
|
||||
if topics then
|
||||
topics[topic] = nil
|
||||
if next(topics) == nil then
|
||||
memberOf[src] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Subscribe a player to a topic. Called server-side by the producer resource.
|
||||
---@param src number serverId of the player to add
|
||||
---@param topic string
|
||||
---@return boolean added false if already subscribed or input is invalid
|
||||
function xLib.pubsub_subscribe(src, topic)
|
||||
if not isLivePlayer(src) or not isValidTopic(topic) then
|
||||
return false
|
||||
end
|
||||
|
||||
local set = subs[topic]
|
||||
if set and set[src] then
|
||||
return false -- idempotent: already subscribed
|
||||
end
|
||||
|
||||
if not set then
|
||||
set = {}
|
||||
subs[topic] = set
|
||||
ownerOf[topic] = GetInvokingResource() or resourceName
|
||||
end
|
||||
set[src] = true
|
||||
|
||||
local topics = memberOf[src]
|
||||
if not topics then
|
||||
topics = {}
|
||||
memberOf[src] = topics
|
||||
end
|
||||
topics[topic] = true
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---Unsubscribe a player from a topic.
|
||||
---@param src number
|
||||
---@param topic string
|
||||
---@return boolean removed false if it was not subscribed
|
||||
function xLib.pubsub_unsubscribe(src, topic)
|
||||
local set = subs[topic]
|
||||
if not set or not set[src] then
|
||||
return false -- idempotent
|
||||
end
|
||||
detach(src, topic)
|
||||
return true
|
||||
end
|
||||
|
||||
---Push data to every player subscribed to a topic. Server-trusted data only.
|
||||
---@param topic string
|
||||
---@param data any
|
||||
---@return integer count number of players notified
|
||||
function xLib.pubsub_publish(topic, data)
|
||||
-- no isValidTopic() here on purpose: an invalid topic never created a set,
|
||||
-- so the lookup below already returns 0 for it without a regex on the hot path
|
||||
local set = subs[topic]
|
||||
if not set then
|
||||
return 0
|
||||
end
|
||||
|
||||
local targets, count = {}, 0
|
||||
for src in pairs(set) do
|
||||
count = count + 1
|
||||
targets[count] = src
|
||||
end
|
||||
|
||||
-- packs the payload once for the whole set instead of once per client
|
||||
xLib.triggerClientEvent(PUBSUB_EVENT, targets, topic, data)
|
||||
return count
|
||||
end
|
||||
|
||||
---@param topic string
|
||||
---@return number[] serverIds a fresh copy, empty if the topic has no subscribers
|
||||
function xLib.pubsub_subscribers(topic)
|
||||
local set = subs[topic]
|
||||
if not set then
|
||||
return {}
|
||||
end
|
||||
|
||||
local list, i = {}, 0
|
||||
for src in pairs(set) do
|
||||
i = i + 1
|
||||
list[i] = src
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
---@param src number
|
||||
---@param topic string
|
||||
---@return boolean
|
||||
function xLib.pubsub_isSubscribed(src, topic)
|
||||
local set = subs[topic]
|
||||
return set ~= nil and set[src] == true
|
||||
end
|
||||
|
||||
---Drop a whole topic and all of its subscribers.
|
||||
---@param topic string
|
||||
---@return integer count number of subscribers that were removed
|
||||
function xLib.pubsub_clear(topic)
|
||||
local set = subs[topic]
|
||||
if not set then
|
||||
return 0
|
||||
end
|
||||
|
||||
local count = 0
|
||||
for src in pairs(set) do
|
||||
local topics = memberOf[src]
|
||||
if topics then
|
||||
topics[topic] = nil
|
||||
if next(topics) == nil then
|
||||
memberOf[src] = nil
|
||||
end
|
||||
end
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
subs[topic] = nil
|
||||
ownerOf[topic] = nil
|
||||
return count
|
||||
end
|
||||
|
||||
-- Remove a leaving player from every topic. serverIds are reused by the server,
|
||||
-- so skipping this would leak a topic's data to whoever inherits the slot.
|
||||
AddEventHandler('playerDropped', function()
|
||||
local src = source
|
||||
local topics = memberOf[src]
|
||||
if not topics then
|
||||
return
|
||||
end
|
||||
|
||||
for topic in pairs(topics) do
|
||||
local set = subs[topic]
|
||||
if set then
|
||||
set[src] = nil
|
||||
if next(set) == nil then
|
||||
subs[topic] = nil
|
||||
ownerOf[topic] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
memberOf[src] = nil
|
||||
end)
|
||||
|
||||
-- Purge the topics a stopped producer owned, so a producer restart never leaves
|
||||
-- orphan subscriptions that keep receiving pushes.
|
||||
AddEventHandler('onResourceStop', function(stopped)
|
||||
if stopped == resourceName then
|
||||
return
|
||||
end
|
||||
|
||||
local orphaned = {}
|
||||
for topic, owner in pairs(ownerOf) do
|
||||
if owner == stopped then
|
||||
orphaned[#orphaned + 1] = topic
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #orphaned do
|
||||
xLib.pubsub_clear(orphaned[i])
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Triggers an event for one or more clients. For an array of players the payload
|
||||
-- is packed once instead of being re-serialised per client.
|
||||
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
|
||||
local pack = msgpack.pack_args
|
||||
|
||||
---@param eventName string
|
||||
---@param targets number | number[] a single serverId, or an array of them
|
||||
---@param ... any
|
||||
function xLib.triggerClientEvent(eventName, targets, ...)
|
||||
if type(targets) == 'number' then
|
||||
return TriggerClientEvent(eventName, targets, ...)
|
||||
end
|
||||
|
||||
local payload = pack(...)
|
||||
local length = #payload
|
||||
for i = 1, #targets do
|
||||
TriggerClientEventInternal(eventName, targets[i], payload, length)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user