1
0
Fork 0
forked from Simnation/Main
Main/resources/[standalone]/utility_lib/client/native.lua
2025-08-12 16:56:50 +02:00

3385 lines
115 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

_G["xPlayer"], _G["source"], _G["developer"] = {}, GetPlayerServerId(PlayerId()), function() end
-- Why?, see that https://www.lua.org/gems/sample.pdf#page=3
local _AddTextEntry, _BeginTextCommandDisplayHelp, _EndTextCommandDisplayHelp, _SetNotificationTextEntry, _AddTextComponentSubstringPlayerName, _DrawNotification, _GetEntityCoords, _World3dToScreen2d, _SetTextScale, _SetTextFont, _SetTextEntry, _SetTextCentre, _AddTextComponentString, _DrawText, _DoesEntityExist, _GetDistanceBetweenCoords, _GetPlayerPed, _TriggerEvent, _TriggerServerEvent = AddTextEntry, BeginTextCommandDisplayHelp, EndTextCommandDisplayHelp, SetNotificationTextEntry, AddTextComponentSubstringPlayerName, DrawNotification, GetEntityCoords, World3dToScreen2d, SetTextScale, SetTextFont, SetTextEntry, SetTextCentre, AddTextComponentString, DrawText, DoesEntityExist, GetDistanceBetweenCoords, GetPlayerPed, TriggerEvent, TriggerServerEvent
local resName = GetCurrentResourceName()
local Keys = {
["ESC"] = 322, ["F1"] = 288, ["F2"] = 289, ["F3"] = 170, ["F5"] = 166, ["F6"] = 167, ["F7"] = 168, ["F8"] = 169, ["F9"] = 56, ["F10"] = 57,
["~"] = 243, ["1"] = 157, ["2"] = 158, ["3"] = 160, ["4"] = 164, ["5"] = 165, ["6"] = 159, ["7"] = 161, ["8"] = 162, ["9"] = 163, ["-"] = 84, ["="] = 83, ["BACKSPACE"] = 177,
["TAB"] = 37, ["Q"] = 44, ["W"] = 32, ["E"] = 38, ["R"] = 45, ["T"] = 245, ["Y"] = 246, ["U"] = 303, ["P"] = 199, ["["] = 39, ["]"] = 40, ["ENTER"] = 18,
["CAPS"] = 137, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["F"] = 23, ["G"] = 47, ["H"] = 74, ["K"] = 311, ["L"] = 182,
["LEFTSHIFT"] = 21, ["Z"] = 20, ["X"] = 73, ["C"] = 26, ["V"] = 0, ["B"] = 29, ["N"] = 249, ["M"] = 244, [","] = 82, ["."] = 81,
["LEFTCTRL"] = 36, ["LEFTALT"] = 19, ["SPACE"] = 22, ["RIGHTCTRL"] = 70,
["HOME"] = 213, ["PAGEUP"] = 10, ["PAGEDOWN"] = 11, ["DELETE"] = 178,
["LEFT"] = 174, ["RIGHT"] = 175, ["TOP"] = 27, ["DOWN"] = 173,
["NENTER"] = 201, ["N4"] = 108, ["N5"] = 60, ["N6"] = 107, ["N+"] = 96, ["N-"] = 97, ["N7"] = 117, ["N8"] = 61, ["N9"] = 118
}
DevModeStatus = false
UtilityLibLoaded = true
local Utility = {
Cache = {
PlayerPedId = PlayerPedId(),
Marker = {},
Object = {},
Dialogue = {},
Blips = {},
N3d = {},
Events = {},
Guards = {},
Scenes = {},
SetData = {},
Frozen = {},
FlowDetector = {},
Textures = {},
--Constant = {},
Settings = {},
EntityStack = {},
Loop = {},
SliceGroups = {}
}
}
UtilityNet = {}
Citizen.CreateThreadNow(function() -- Load Classes
if UFAPI then -- if the utility framework API is loaded
if resName == "utility_lib" then
_G["Utility"] = Utility
else
_G["UtilityLibrary"] = Utility
end
else -- the utility framework API is not loaded :(
_G["Utility"] = Utility
end
end)
UseDelete = function(boolean)
Utility.Cache.Settings.UseDelete = boolean
end
--// Emitter //--
On = function(type, function_id, fake_triggerable)
RegisterNetEvent("Utility:On:".. (fake_triggerable and "!" or "") ..type)
local event = AddEventHandler("Utility:On:".. (fake_triggerable and "!" or "") ..type, function_id)
table.insert(Utility.Cache.Events, event)
return event
end
--// Custom/Improved Native //--
_G.old_TaskVehicleDriveToCoord = TaskVehicleDriveToCoord
TaskVehicleDriveToCoord = function(ped, vehicle, destination, speed, stopRange)
old_TaskVehicleDriveToCoord(ped, vehicle, destination, speed or 10.0, 0, GetEntityModel(vehicle), 2883621, stopRange or 1.0)
end
_G.old_DisableControlAction = DisableControlAction
DisableControlAction = function(group, control, disable)
disable = disable ~= nil and disable or true
if Keys[string.upper(group)] then
return old_DisableControlAction(0, Keys[string.upper(group)], control)
else
return old_DisableControlAction(group, control, disable) -- Retro compatibility
end
end
DisableControlForSeconds = function(control, seconds)
local sec = seconds
Citizen.CreateThread(function()
while sec > 0 do
Citizen.Wait(1000)
sec = sec - 1
end
return
end)
Citizen.CreateThread(function()
while sec > 0 do
DisableControlAction(Keys[string.upper(control)])
Citizen.Wait(1)
end
return
end)
end
_G.old_IsControlJustPressed = IsControlJustPressed
IsControlJustPressed = function(key, _function, description)
if type(key) == "number" then
local inputGroup = key
local control = _function
return old_IsControlJustPressed(inputGroup, control)
end
developer("^2Created^0", "key map", key)
local input = "keyboard"
key = key:lower()
if key:find("mouse_") or key:find("iom_wheel") then
input = "mouse_button"
elseif key:find("_index") then
input = "pad_digitalbutton"
elseif key:find("iom_axis") then
input = "pad_axis"
end
RegisterKeyMapping('utility '..resName..' '..key, (description or ''), input, key)
local eventHandler = nil
Citizen.CreateThread(function()
Citizen.Wait(500)
eventHandler = RegisterNetEvent("Utility:Pressed_"..resName.."_"..key, _function)
table.insert(Utility.Cache.Events, eventHandler)
end)
end
ShowNotification = function(msg)
_SetNotificationTextEntry('STRING')
_AddTextComponentSubstringPlayerName(msg)
_DrawNotification(false, true)
end
ButtonNotification = function(msg)
if string.match(msg, "{.*}") then
msg = string.multigsub(msg, {"{A}","{B}", "{C}", "{D}", "{E}", "{F}", "{G}", "{H}", "{L}", "{M}", "{N}", "{O}", "{P}", "{Q}", "{R}", "{S}", "{T}", "{U}", "{V}", "{W}", "{X}", "{Y}", "{Z}"}, {"~INPUT_VEH_FLY_YAW_LEFT~", "~INPUT_SPECIAL_ABILITY_SECONDARY~", "~INPUT_LOOK_BEHIND~", "~INPUT_MOVE_LR~", "~INPUT_CONTEXT~", "~INPUT_ARREST~", "~INPUT_DETONATE~", "~INPUT_VEH_ROOF~", "~INPUT_CELLPHONE_CAMERA_FOCUS_LOCK~", "~INPUT_INTERACTION_MENU~", "~INPUT_REPLAY_ENDPOINT~" , "~INPUT_FRONTEND_PAUSE~", "~INPUT_FRONTEND_LB~", "~INPUT_RELOAD~", "~INPUT_MOVE_DOWN_ONLY~", "~INPUT_MP_TEXT_CHAT_ALL~", "~INPUT_REPLAY_SCREENSHOT~", "~INPUT_NEXT_CAMERA~", "~INPUT_MOVE_UP_ONLY~", "~INPUT_VEH_HOTWIRE_LEFT~", "~INPUT_VEH_DUCK~", "~INPUT_MP_TEXT_CHAT_TEAM~", "~INPUT_HUD_SPECIAL~"})
end
_AddTextEntry('ButtonNotification'..string.len(msg), msg)
_BeginTextCommandDisplayHelp('ButtonNotification'..string.len(msg))
_EndTextCommandDisplayHelp(0, false, true, -1)
end
ButtonFor = function(msg, ms)
local timer = GetGameTimer()
Citizen.CreateThread(function()
while (GetGameTimer() - timer) < (ms or 5000) do
ButtonNotification(msg)
Citizen.Wait(1)
end
end)
end
FloatingNotification = function(msg, coords)
_AddTextEntry('FloatingNotification', msg)
SetFloatingHelpTextWorldPosition(1, coords)
SetFloatingHelpTextStyle(1, 1, 2, -1, 3, 0)
_BeginTextCommandDisplayHelp('FloatingNotification')
_EndTextCommandDisplayHelp(2, false, false, -1)
end
MakeEntityFaceEntity = function(entity1, entity2, whatentity)
local coords1 = _GetEntityCoords(entity1, true)
local coords2 = _GetEntityCoords(entity2, true)
if whatentity then
local heading = GetHeadingFromVector_2d(coords2.x - coords1.x, coords2.y - coords1.y)
SetEntityHeading(entity1, heading)
else
local heading = GetHeadingFromVector_2d(coords1.x - coords2.x, coords1.y - coords2.y)
SetEntityHeading(entity2, heading)
end
end
DrawText3Ds = function(coords, text, scale, font, rectangle)
if coords then
local onScreen, _x, _y = _World3dToScreen2d(coords.x, coords.y, coords.z)
if onScreen then
_SetTextScale(scale or 0.35, scale or 0.35)
_SetTextFont(font or 4)
_SetTextEntry("STRING")
_SetTextCentre(1)
_AddTextComponentString(text)
_DrawText(_x, _y)
if rectangle then
local factor = (string.len(text))/370
local _, count = string.gsub(factor, "\n", "\n") * 0.025
if count == nil then count = 0 end
DrawRect(_x, _y + 0.0125, 0.025 + factor, 0.025 + count, 0, 0, 0, 90)
end
end
end
end
_G.old_TaskPlayAnim = TaskPlayAnim
TaskPlayAnim = function(ped, animDictionary, ...)
if not HasAnimDictLoaded(animDictionary) then
RequestAnimDict(animDictionary)
while not HasAnimDictLoaded(animDictionary) do Citizen.Wait(1) end
end
old_TaskPlayAnim(ped, animDictionary, ...)
RemoveAnimDict(animDictionary)
end
TaskEasyPlayAnim = function(dict, anim, move, duration)
if move == nil then move = 51 end
if duration == nil then duration = -1 end
TaskPlayAnim(PlayerPedId(), dict, anim, 2.0, 2.0, duration, move, 0)
if duration > -1 or duration > 0 then
Citizen.Wait(duration)
end
end
_G.old_CreateObject = CreateObject
CreateObject = function(model, ...)
local modelHash = model
if type(model) == "string" then
modelHash = GetHashKey(model)
end
if not IsModelValid(modelHash) then
error("Model \""..model.."\" loaded from \""..GetCurrentResourceName().."\" is not valid^0")
return
end
if not HasModelLoaded(modelHash) then
RequestModel(modelHash);
while not HasModelLoaded(modelHash) do Citizen.Wait(1); end
end
local obj = old_CreateObject(modelHash, ...)
if Utility.Cache.Settings.UseDelete then
table.insert(Utility.Cache.EntityStack, obj)
end
SetModelAsNoLongerNeeded(modelHash)
local netId = 0
if NetworkGetEntityIsNetworked(obj) then
netId = ObjToNet(obj)
SetNetworkIdExistsOnAllMachines(netId, true)
SetNetworkIdCanMigrate(netId, true)
end
return obj, netId
end
_G.old_CreatePed = CreatePed
CreatePed = function(modelHash, ...)
if type(modelHash) == "string" then
modelHash = GetHashKey(modelHash)
end
if not HasModelLoaded(modelHash) then
RequestModel(modelHash);
while not HasModelLoaded(modelHash) do Citizen.Wait(1); end
end
local ped = old_CreatePed(0, modelHash, ...)
SetModelAsNoLongerNeeded(modelHash)
local netId = 0
if NetworkGetEntityIsNetworked(ped) then
netId = PedToNet(ped)
SetNetworkIdExistsOnAllMachines(netId, true)
SetNetworkIdCanMigrate(netId, true)
end
return ped, netId
end
SetPedStatic = function(entity, active)
FreezeEntityPosition(entity, active)
SetEntityInvincible(entity, active)
SetBlockingOfNonTemporaryEvents(entity, active)
end
_G.old_CreateVehicle = CreateVehicle
CreateVehicle = function(modelHash, ...)
if type(modelHash) == "string" then
modelHash = GetHashKey(modelHash)
end
if not HasModelLoaded(modelHash) then
RequestModel(modelHash);
while not HasModelLoaded(modelHash) do Citizen.Wait(1); end
end
local veh = old_CreateVehicle(modelHash, ...)
SetModelAsNoLongerNeeded(modelHash)
local netId = 0
if NetworkGetEntityIsNetworked(veh) then
netId = VehToNet(veh)
SetNetworkIdExistsOnAllMachines(netId, true)
SetNetworkIdCanMigrate(netId, true)
end
return veh, netId
end
_G.old_DeleteEntity = DeleteEntity
DeleteEntity = function(entity, isnetwork)
if not isnetwork then
local attempts = 0
NetworkRequestControlOfEntity(entity)
-- entity = entityHandler
while not NetworkRequestControlOfEntity(entity) and attempts < 10 do
attempts = attempts + 1
Citizen.Wait(1)
end
if not IsEntityAMissionEntity(entity) then
SetEntityAsMissionEntity(entity)
end
old_DeleteEntity(entity)
else
-- entity = networkID
NetworkRequestControlOfNetworkId(entity)
local new_entity = NetworkGetEntityFromNetworkId(entity)
local attempts = 0
while not NetworkRequestControlOfEntity(new_entity) and attempts < 10 do
attempts = attempts + 1
Citizen.Wait(1)
end
SetEntityAsMissionEntity(new_entity)
old_DeleteEntity(new_entity)
end
end
_G.old_GetPlayerName = GetPlayerName
GetPlayerName = function(id)
return old_GetPlayerName(id or PlayerId())
end
_G.old_PlayerPedId = PlayerPedId
PlayerPedId = function()
if not _DoesEntityExist(Utility.Cache.PlayerPedId) then Utility.Cache.PlayerPedId = old_PlayerPedId() end
return Utility.Cache.PlayerPedId
end
-- Loop
-- Before _break
StopLoop = function(id)
Utility.Cache.Loop[id].status = false
end
CreateLoop = function(_function, tickTime, dontstart)
local loopId = RandomId(5)
Utility.Cache.Loop[loopId] = {
status = true,
func = _function,
tick = tickTime
}
if dontstart ~= false then
Citizen.CreateThread(function()
while Utility.Cache.Loop[loopId] and Utility.Cache.Loop[loopId].status do
_function(loopId)
Citizen.Wait(tickTime or 1)
end
end)
end
return loopId
end
PauseLoop = function(loopId, delay)
Citizen.SetTimeout(delay or 0, function()
print("Pausing loop "..loopId)
Utility.Cache.Loop[loopId].status = false
end)
end
ResumeLoop = function(loopId, delay)
local current = Utility.Cache.Loop[loopId]
Citizen.SetTimeout(delay or 0, function()
print("Resuming loop "..loopId)
current.status = true
Citizen.CreateThread(function()
while current and current.status do
current.func(loopId)
Citizen.Wait(current.tick or 1)
end
end)
end)
end
GetWorldClosestPed = function(radius)
local closest = 0
local AllFoundedPed = GetGamePool("CPed")
local coords = _GetEntityCoords(PlayerPedId())
local minDistance = radius + 5.0
for i=1, #AllFoundedPed do
local distance = _GetDistanceBetweenCoords(coords, _GetEntityCoords(AllFoundedPed[i]), false)
if distance <= radius and AllFoundedPed[i] ~= PlayerPedId() then
if minDistance > distance then
minDistance = distance
closest = AllFoundedPed[i]
end
end
end
return closest, AllFoundedPed
end
GetWorldClosestPlayer = function(radius)
local closest = 0
local AllPlayers = {}
local minDistance = radius + 5.0
local AllFoundedPed = GetGamePool("CPed")
local coords = _GetEntityCoords(PlayerPedId())
for i=1, #AllFoundedPed do
if IsPedAPlayer(AllFoundedPed[i]) then
table.insert(AllPlayers, NetworkGetPlayerIndexFromPed(AllFoundedPed[i]))
local distance = _GetDistanceBetweenCoords(coords, _GetEntityCoords(AllFoundedPed[i]), false)
if distance <= radius and AllFoundedPed[i] ~= PlayerPedId() then
if minDistance > distance then
minDistance = distance
closest = NetworkGetPlayerIndexFromPed(AllFoundedPed[i])
end
end
end
end
return closest, AllPlayers
end
GetEntitySurfaceMaterial = function(entity)
local coords = GetEntityCoords(entity)
local shape_result = StartShapeTestCapsule(coords.x,coords.y,coords.z,coords.x,coords.y,coords.z-2.5, 2, 1, entity, 7)
local _, hitted, _, _, materialHash = GetShapeTestResultIncludingMaterial(shape_result)
return materialHash, hitted
end
GetLoadoutOfPed = function(ped)
local list = ESX.GetWeaponList()
local loadout = {}
for i=1, #list, 1 do
local hash = GetHashKey(list.name)
if HasPedGotWeapon(ped, hash, false) then
table.insert(loadout, {name = list.name, hash = hash, ammo = GetAmmoInPedWeapon(ped, hash)})
end
end
return loadout
end
local old_FreezeEntityPosition = FreezeEntityPosition
FreezeEntityPosition = function(entity, active)
Utility.Cache.Frozen[entity] = active
old_FreezeEntityPosition(entity, active)
end
IsEntityFrozen = function(entity)
return Utility.Cache.Frozen[entity] == true
end
GetNearestValue = function(v, all_v)
local diff = 100 * 100000000000
local _i = 0
for i=1, #all_v do
local c_diff = math.abs(all_v[i] - v)
if (c_diff < diff) then
diff = c_diff
n = all_v[i]
_i = i
end
end
return n, diff, _i
end
--// Frameworks integration //--
-- Init
StartESX = function(eventName, second_job)
Citizen.CreateThreadNow(function()
ESX = exports["es_extended"]:getSharedObject()
while ESX.GetPlayerData().job == nil do
Citizen.Wait(1)
end
uPlayer = ESX.GetPlayerData()
xPlayer = uPlayer
if second_job ~= nil then
while ESX.GetPlayerData()[second_job] == nil do
Citizen.Wait(1)
end
uPlayer = ESX.GetPlayerData()
xPlayer = uPlayer
end
if second_job ~= nil then
RegisterNetEvent('esx:set'..string.upper(second_job:sub(1,1))..second_job:sub(2), function(job)
uPlayer[second_job] = job
xPlayer = uPlayer
end)
end
RegisterNetEvent('esx:setJob', function(job)
uPlayer.job = job
xPlayer = uPlayer
if OnJobUpdate then
OnJobUpdate()
end
end)
end)
end
StartQB = function()
QBCore = exports['qb-core']:GetCoreObject()
uPlayer = QBCore.Functions.GetPlayerData()
Player = uPlayer
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
uPlayer = QBCore.Functions.GetPlayerData()
Player = uPlayer
end)
RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo)
uPlayer.job = JobInfo
Player = uPlayer
if OnJobUpdate then
OnJobUpdate()
end
end)
end
StartFramework = function()
if GetResourceState("qb-core") ~= "missing" then
StartQB()
return true
elseif GetResourceState("es_extended") ~= "missing" then
StartESX()
return true
end
end
-- Job
GetDataForJob = function(job)
local job_info = promise:new()
if GetResourceState("es_extended") == "started" then
ESX.TriggerServerCallback("Utility:GetJobData", function(worker)
job_info:resolve(worker)
end, job)
elseif GetResourceState("qb-core") == "started" then
QBCore.Functions.TriggerCallback("Utility:GetJobData", function(worker)
job_info:resolve(worker)
end, job)
end
job_info = Citizen.Await(job_info)
return #job_info, job_info
end
--// Advanced script creation //--
local _GetOnHandObject = 0
GetOnHandObject = function()
return _GetOnHandObject
end
TakeObjectOnHand = function(ped, entityToGrab, zOffset, xPos, yPos, zPos, xRot, yRot, zRot)
developer("^2Taking^0", "object", entityToGrab.." ("..GetEntityModel(entityToGrab)..")")
if type(entityToGrab) == "number" then -- Send an entity ID (Use already exist entity)
NetworkRequestControlOfEntity(entityToGrab)
while not NetworkHasControlOfEntity(entityToGrab) do Citizen.Wait(1) end
TaskPlayAnim(ped, "anim@heists@box_carry@", "idle", 3.0, 3.0, -1, 63, 0, 0, 0, 0)
Citizen.Wait(100)
AttachEntityToEntity(entityToGrab, ped, GetPedBoneIndex(ped, 60309), xPos or 0.2, yPos or 0.08, zPos or 0.2, xRot or -45.0, yRot or 290.0, zRot or 0.0, true, true, false, true, 1, true)
_GetOnHandObject = entityToGrab
elseif type(entityToGrab) == "string" then -- Send a model name (New object created)
local coords = _GetEntityCoords(ped)
local prop = CreateObject(entityToGrab, coords + vector3(0.0, 0.0, zOffset or 0.2), true, false, false)
SetEntityAsMissionEntity(prop)
TaskPlayAnim(ped, "anim@heists@box_carry@", "idle", 3.0, -8, -1, 63, 0, 0, 0, 0)
Citizen.Wait(100)
AttachEntityToEntity(prop, ped, GetPedBoneIndex(ped, 60309), xPos or 0.2, yPos or 0.08, zPos or 0.2, xRot or -45.0, yRot or 290.0, zRot or 0.0, true, true, false, true, 1, true)
_GetOnHandObject = prop
return prop
end
end
DropObjectFromHand = function(entityToDrop, delete)
if delete then
developer("^1Deleting^0","from hand", entityToDrop)
DeleteEntity(entityToDrop)
else
developer("^3Dont delete^0","from hand", entityToDrop)
DetachEntity(entityToDrop)
SetEntityCoords(entityToDrop, GetOffsetFromEntityInWorldCoords(entityToDrop, 0, 0.5, 0))
PlaceObjectOnGroundProperly(entityToDrop)
FreezeEntityPosition(entityToDrop, true)
end
ClearPedTasks(PlayerPedId())
_GetOnHandObject = 0
end
IsInRadius = function(coords1, coords2, radius, debugSphere)
local distance = #(coords1-coords2)
if debugSphere then
DrawSphere(coords2, radius, 255, 0, 0, 0.5)
end
return distance < radius
end
IsNearCoords = function(coords, radius, debugSphere)
local distance = #(GetEntityCoords(PlayerPedId())-coords)
if debugSphere then
DrawSphere(coords, radius, 255, 0, 0, 0.5)
end
return distance < radius
end
GenerateRandomCoords = function(coords, radius, heading)
local x = coords.x + math.random(-radius, radius)
local y = coords.y + math.random(-radius, radius)
local _, z = GetGroundZFor_3dCoord(x,y,200.0,0)
if heading then
return vector3(x,y,z), math.random(0.0, 360.0)
end
return vector3(x,y,z)
end
--// Managing data (like table, but more easy to use) //--
SetFor = function(id, property, value)
-- If id dont already exist register it for store data
if Utility.Cache["SetData"][id] == nil then
Utility.Cache["SetData"][id] = {}
end
if type(property) == "table" then -- Table
for k,v in pairs(property) do
if type(v) == "function" then
developer("^2Setting^0", "data", "("..id..") ["..k.." = "..tostring(v).."] {table}")
else
developer("^2Setting^0", "data", "("..id..") ["..k.." = "..json.encode(v).."] {table}")
end
Utility.Cache["SetData"][id][k] = v
end
else -- Single
if type(value) == "function" then
developer("^2Setting^0", "data", "("..id..") ["..property.." = "..tostring(value).."] {single}")
else
developer("^2Setting^0", "data", "("..id..") ["..property.." = "..json.encode(value).."] {single}")
end
Utility.Cache["SetData"][id][property] = value
end
end
GetFrom = function(id, property)
if property == nil then
property = "not defined"
end
developer("^3Getting^0", "data", "("..id..") ["..property.."]")
if Utility.Cache["SetData"][id] ~= nil then
if property == "not defined" then
return Utility.Cache["SetData"][id]
else
return Utility.Cache["SetData"][id][property]
end
else
return nil
end
end
--// Slices //--
local sliceSize = 100.0
local slicesLength = 8100
local sliceCollumns = slicesLength / sliceSize
function GetSliceColRowFromCoords(coords)
local row = math.floor((coords.x) / sliceSize)
local col = math.floor((coords.y) / sliceSize)
return col, row
end
function GetWorldCoordsFromSlice(slice)
local col = math.floor(slice / sliceCollumns)
local row = slice % sliceCollumns
return vec3((row * sliceSize), (col * sliceSize), 0.0)
end
function GetSliceIdFromColRow(col, row)
return math.floor(col * sliceCollumns + row)
end
function GetSliceFromCoords(pos)
local col, row = GetSliceColRowFromCoords(pos)
return GetSliceIdFromColRow(col, row)
end
function GetEntitySlice(ped)
return GetSliceFromCoords(GetEntityCoords(ped))
end
function GetPlayerSlice(player)
local ped = GetPlayerPed(player)
return GetSliceFromCoords(GetEntityCoords(ped))
end
function GetSelfSlice()
local ped = PlayerPedId()
return GetSliceFromCoords(GetEntityCoords(ped))
end
function IsOnScreen(coords)
local onScreen, _x, _y = World3dToScreen2d(coords.x, coords.y, coords.z)
return onScreen
end
function GetSurroundingSlices(slice)
local top = slice - sliceCollumns
local bottom = slice + sliceCollumns
local right = slice - 1
local left = slice + 1
local topright = slice - sliceCollumns - 1
local topleft = slice - sliceCollumns + 1
local bottomright = slice + sliceCollumns - 1
local bottomleft = slice + sliceCollumns + 1
return {top, bottom, left, right, topright, topleft, bottomright, bottomleft}
end
function DrawDebugSlice(slice, drawSorroundings, zOffset)
local drawRects = {}
local sorrounding = drawSorroundings and GetSurroundingSlices(slice) or {}
for k,v in pairs({slice, table.unpack(sorrounding)}) do
local bottomLeftSlice = slice + sliceCollumns + 1
table.insert(drawRects, {
GetWorldCoordsFromSlice(v) + vec3(0.0,0.0, zOffset or 500.0),
GetWorldCoordsFromSlice(bottomLeftSlice) + vec3(0.0,0.0, zOffset or 500.0),
})
end
-- Predefined colors to distinguish slices
local colors = {
{255, 0, 0},
{0, 255, 0},
{0, 0, 255},
{255, 255, 0},
{0, 255, 255},
{255, 0, 255},
{255, 255, 255}
}
-- Draw rects
for i=1, #drawRects do
local color = colors[i % #colors + 1]
DrawBox(drawRects[i][1], drawRects[i][2], color[1], color[2], color[3], 150)
end
end
function SliceUsed(slice)
return Utility.Cache.SliceGroups[slice] or false
end
function SetSliceUsed(slice, value)
slice = tonumber(slice)
if value then
Utility.Cache.SliceGroups[slice] = value
else
Utility.Cache.SliceGroups[slice] = nil
end
end
--// Marker/Object/Blip //--
-- Marker
RandomId = function(length)
length = length or 5
local maxvalue = ""
for i=1, length do
maxvalue = maxvalue.."9"
end
return math.random(0, maxvalue)
end
CreateMarker = function(id, coords, render_distance, interaction_distance, options)
if DoesExist("m", id) then
Citizen.Wait(100)
return
else
if type(coords) ~= "vector3" then
developer("^1Error^0","You can use only vector3 for coords!",id)
return
end
id = string.gsub(id, "{r}", RandomId())
developer("^2Created^0","Marker",id)
local _marker = {
render_distance = render_distance,
interaction_distance = interaction_distance,
coords = coords,
slice = (options and options.slice == "ignore") and "ignore" or GetSliceFromCoords(coords)
}
-- Options
if type(options) == "table" then
if options.rgb ~= nil then -- Marker
_marker.type = 1
_marker.rgb = options.rgb
elseif options.text ~= nil then -- 3d Text
_marker.type = 0
_marker.text = options.text
else
_marker.type = 1
_marker.rgb = {options[1], options[2], options[3]}
end
if options.type ~= nil and type(options.type) == "number" then _marker._type = options.type end
if options.direction ~= nil and type(options.direction) == "vector3" then _marker._direction = options.direction end
if options.rotation ~= nil and type(options.rotation) == "vector3" then _marker._rot = options.rotation end
if options.scale ~= nil and type(options.scale) == "vector3" then _marker._scale = options.scale end
if options.alpha ~= nil and type(options.alpha) == "number" then _marker.alpha = options.alpha end
if options.animation ~= nil and type(options.animation) == "boolean" then _marker.anim = options.animation end
if options.job ~= nil then _marker.job = options.job end
if options.notify ~= nil then
local notify = string.multigsub(options.notify, {"{A}","{B}", "{C}", "{D}", "{E}", "{F}", "{G}", "{H}", "{L}", "{M}", "{N}", "{O}", "{P}", "{Q}", "{R}", "{S}", "{T}", "{U}", "{V}", "{W}", "{X}", "{Y}", "{Z}"}, {"~INPUT_VEH_FLY_YAW_LEFT~", "~INPUT_SPECIAL_ABILITY_SECONDARY~", "~INPUT_LOOK_BEHIND~", "~INPUT_MOVE_LR~", "~INPUT_CONTEXT~", "~INPUT_ARREST~", "~INPUT_DETONATE~", "~INPUT_VEH_ROOF~", "~INPUT_CELLPHONE_CAMERA_FOCUS_LOCK~", "~INPUT_INTERACTION_MENU~", "~INPUT_REPLAY_ENDPOINT~" , "~INPUT_FRONTEND_PAUSE~", "~INPUT_FRONTEND_LB~", "~INPUT_RELOAD~", "~INPUT_MOVE_DOWN_ONLY~", "~INPUT_MP_TEXT_CHAT_ALL~", "~INPUT_REPLAY_SCREENSHOT~", "~INPUT_NEXT_CAMERA~", "~INPUT_MOVE_UP_ONLY~", "~INPUT_VEH_HOTWIRE_LEFT~", "~INPUT_VEH_DUCK~", "~INPUT_MP_TEXT_CHAT_TEAM~", "~INPUT_HUD_SPECIAL~"})
_marker.notify = notify
end
elseif type(options) == "string" then
_marker.type = 0
_marker.text = options
end
Utility.Cache.Marker[id] = _marker -- Sync the local table
_TriggerEvent("Utility:Create", "Marker", id, _marker) -- Sync the table in the utility_lib
end
end
SetMarker = function(id, _type, key, value)
if (type(value) ~= _type) and (value ~= nil) then
developer("^1Error^0", key.." can be only a ".._type, "[Marker]")
return
end
if DoesExist("marker", id) then
Utility.Cache.Marker[id][key] = value
_TriggerEvent("Utility:Edit", "Marker", id, key, value)
else
developer("^1Error^0", "Unable to edit the marker as it does not exist", id)
end
end
SetMarkerType = function(id, _type)
SetMarker(id, "number", "_type", _type)
end
SetMarkerDirection = function(id, direction)
SetMarker(id, "vector3", "_direction", direction)
end
SetMarkerRotation = function(id, rot)
SetMarker(id, "vector3", "_rot", rot)
end
SetMarkerScale = function(id, scale)
SetMarker(id, "vector3", "_scale", scale)
end
SetMarkerColor = function(id, rgb)
SetMarker(id, "table", "rgb", rgb)
end
---
SetMarkerCoords = function(id, coords)
SetMarker(id, "string", "slice", tostring(GetSliceFromCoords(coords)))
SetMarker(id, "vector3", "coords", coords)
end
SetMarkerRenderDistance = function(id, render_distance)
SetMarker(id, "number", "render_distance", render_distance)
end
SetMarkerInteractionDistance = function(id, interaction_distance)
SetMarker(id, "number", "interaction_distance", interaction_distance)
end
---
SetMarkerAlpha = function(id, alpha)
SetMarker(id, "number", "alpha", alpha)
end
SetMarkerAnimation = function(id, active)
SetMarker(id, "boolean", "anim", active)
end
SetMarkerDrawOnEntity = function(id, active)
SetMarker(id, "boolean", "draw_entity", active)
end
SetMarkerNotify = function(id, text)
if type(text) == "string" then
text = string.multigsub(text, {"{A}","{B}", "{C}", "{D}", "{E}", "{F}", "{G}", "{H}", "{L}", "{M}", "{N}", "{O}", "{P}", "{Q}", "{R}", "{S}", "{T}", "{U}", "{V}", "{W}", "{X}", "{Y}", "{Z}"}, {"~INPUT_VEH_FLY_YAW_LEFT~", "~INPUT_SPECIAL_ABILITY_SECONDARY~", "~INPUT_LOOK_BEHIND~", "~INPUT_MOVE_LR~", "~INPUT_PICKUP~", "~INPUT_ARREST~", "~INPUT_DETONATE~", "~INPUT_VEH_ROOF~", "~INPUT_CELLPHONE_CAMERA_FOCUS_LOCK~", "~INPUT_INTERACTION_MENU~", "~INPUT_REPLAY_ENDPOINT~" , "~INPUT_FRONTEND_PAUSE~", "~INPUT_FRONTEND_LB~", "~INPUT_RELOAD~", "~INPUT_MOVE_DOWN_ONLY~", "~INPUT_MP_TEXT_CHAT_ALL~", "~INPUT_REPLAY_SCREENSHOT~", "~INPUT_NEXT_CAMERA~", "~INPUT_MOVE_UP_ONLY~", "~INPUT_VEH_HOTWIRE_LEFT~", "~INPUT_VEH_DUCK~", "~INPUT_MP_TEXT_CHAT_TEAM~", "~INPUT_HUD_SPECIAL~"})
end
SetMarker(id, "string", "notify", text)
end
-- 3dText
Set3dText = function(id, text)
SetMarker(id, "string", "text", text)
end
Set3dTextScale = function(id, scale)
SetMarker(id, "number", "_scale", scale)
end
Set3dTextDrawRect = function(id, active)
SetMarker(id, "boolean", "rect", active)
end
Set3dTextFont = function(id, font)
SetMarker(id, "number", "font", font)
end
DeleteMarker = function(id)
if not DoesExist("m", id) then
Citizen.Wait(100)
return
else
developer("^1Deleted^0","Marker",id)
Utility.Cache.Marker[id] = nil
_TriggerEvent("Utility:Remove", "Marker", id)
ClearAllHelpMessages()
end
end
-- Object
CreateiObject = function(id, model, pos, heading, interaction_distance, network, job)
developer("^2Created^0 Object "..id.." ("..model..")")
local obj
if network ~= nil then
obj = CreateObject(GetHashKey(model), pos.x,pos.y,pos.z, network, false, false) or nil
else
obj = CreateObject(GetHashKey(model), pos.x,pos.y,pos.z, true, false, false) or nil
end
SetEntityHeading(obj, heading)
SetEntityAsMissionEntity(obj, true, true)
FreezeEntityPosition(obj, true)
SetModelAsNoLongerNeeded(hash)
_object = {
obj = obj,
coords = pos,
interaction_distance = interaction_distance or 3.0,
slice = GetSliceFromCoords(pos),
job = job
}
Utility.Cache.Object[id] = _object -- Sync the local table
_TriggerEvent("Utility:Create", "Object", id, _object) -- Sync the table in the utility_lib
return obj, _GetEntityCoords(obj)
end
DeleteiObject = function(id, delete)
developer("^1Deleted^0","Object",id)
if delete then
DeleteEntity(Utility.Cache.Object[id].obj)
end
Utility.Cache.Object[id] = nil
_TriggerEvent("Utility:Remove", "Object", id)
end
-- Blip
CreateBlip = function(name, coords, sprite, colour, scale)
developer("^2Created^0","Blip",name)
local blip = AddBlipForCoord(coords)
SetBlipSprite (blip, sprite)
SetBlipScale (blip, scale or 1.0)
SetBlipColour (blip, colour)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName('STRING')
_AddTextComponentSubstringPlayerName(name)
EndTextCommandSetBlipName(blip)
return blip
end
CreateJobBlip = function(name, coords, job, sprite, colour, scale)
_TriggerEvent("Utility:Create", "Blips", math.random(10000, 99999), {
name = name,
coords = coords,
job = job,
sprite = sprite,
colour = colour,
scale = scale or 1.0
})
end
-- Get/Edit
SetIdOf = function(type, id, new_id)
if type:lower() == "marker" or type:lower() == "m" then
type = "Marker"
elseif type:lower() == "object" or type:lower() == "o" then
type = "Object"
else
return nil
end
if DoesExist(type, id) then
Utility.Cache[type][new_id] = Utility.Cache[type][id]
Utility.Cache[type][id] = nil
else
developer("^1Error^0", "Unable to set id of the "..type.." as it does not exist", id)
return
end
developer("^3Change^0", "Setted id to "..new_id.." of the id", id)
_TriggerEvent("Utility:Remove", type, id)
_TriggerEvent("Utility:Create", type, new_id, Utility.Cache[type][new_id]) -- Sync the table in the utility_lib
end
GetDistanceFrom = function(type, id)
if type:lower() == "marker" or type:lower() == "m" then
type = "Marker"
elseif type:lower() == "object" or type:lower() == "o" then
type = "Object"
else
return nil
end
local distance = 0.0
if Utility.Cache[type][id].coords ~= nil then
return _GetDistanceBetweenCoords(_GetEntityCoords(PlayerPedId()), Utility.Cache[type][id].coords, true)
else
return false
end
end
GetCoordOf = function(type, id)
if type:lower() == "marker" or type:lower() == "m" then
type = "Marker"
elseif type:lower() == "object" or type:lower() == "o" then
type = "Object"
else
return nil
end
if DoesExist(type, id) then
return Utility.Cache[type][id].coords
else
developer("^1Error^0", "Unable to get the coords of the id", id)
return false
end
end
DoesExist = function(type, id)
if type:lower() == "marker" or type:lower() == "m" then
type = "Marker"
elseif type:lower() == "object" or type:lower() == "o" then
type = "Object"
elseif type:lower() == "n3d" or type:lower() == "n" then
type = "N3d"
else
return nil
end
if Utility.Cache[type][id] ~= nil then
return true
else
return false
end
end
--// Camera //--
CreateCamera = function(coords, rotation, active, shake)
local cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
SetCamCoord(cam, coords)
if rotation ~= nil then
SetCamRot(cam, rotation.x, rotation.y, rotation.z)
end
if shake ~= nil then
ShakeCam(cam, shake.type or "", shake.amount or 0.0)
end
if active then
SetCamActive(cam, true)
RenderScriptCams(1, 1, 1500)
end
return cam
end
SwitchBetweenCam = function(old_cam, cam, duration)
SetCamActiveWithInterp(cam, old_cam, duration or 1500, 1, 1)
Citizen.Wait(duration or 1500)
DestroyCam(old_cam)
end
--// Other // --
DevMode = function(state, time, format)
DevModeStatus = state
if time == nil then time = true end
format = format or "%s %s %s"
if state then
developer = function(action, type, id)
local _, _, _, hour, minute, second = GetLocalTime()
if time then
if type == nil then
print(hour..":"..minute..":"..second.." - "..action)
else
print(hour..":"..minute..":"..second.." - "..string.format(format, action, type, id or ""))
end
else
if type == nil then
print(action)
else
print(string.format(format, action, type, id or ""))
end
end
end
else
developer = function() end
end
end
ReplaceTexture = function(prop, textureName, url, width, height)
local txName = prop..":"..textureName..":" -- prop:textureName
local txId = txName..":"..url -- txName:url (prop:textureName:url)
if not Utility.Cache.Textures[txId] then -- If texture with same prop, texture name and url does not exist we create it (to prevent 2 totally same dui)
local txd = CreateRuntimeTxd(txName..'duiTxd')
local duiObj = CreateDui(url, width, height)
local dui = GetDuiHandle(duiObj)
CreateRuntimeTextureFromDuiHandle(txd, txName..'duiTex', dui)
Utility.Cache.Textures[txId] = true
end
AddReplaceTexture(prop, textureName, txName.."duiTxd", txName.."duiTex")
end
printd = function(_table, advanced)
if advanced then
local printTable_cache = {}
local function sub_printTable(t, indent)
if (printTable_cache[tostring(t)]) then
print(indent.."*"..tostring(t))
else
printTable_cache[tostring(t)] = true
if (type(t) == "table") then
for pos,val in pairs(t) do
if (type(val) == "table") then
print(indent.."["..pos.."] => "..tostring(t).. " {" )
sub_printTable(val, indent..string.rep(" ", string.len(pos)+8))
print(indent..string.rep(" ", string.len(pos)+6 ).."}")
elseif (type(val) == "string") then
print(indent.."["..pos.."] => \"" .. val .. "\"")
else
print(indent.."["..pos.."] => "..tostring(val))
end
end
else
print(indent..tostring(t))
end
end
end
if (type(_table) == "table") then
print(tostring(_table).." {")
sub_printTable(_table, " ")
print("}")
else
developer("^1Error^0", "error dumping table ".._table.." why isnt a table")
end
else
if type(_table) == "table" then
print(json.encode(_table, {indent = true}))
else
developer("^1Error^0", "error dumping table ".._table.." why isnt a table")
end
end
end
local string_gsub = string.gsub
string.multigsub = function(string, table, new)
if type(table) then
for i=1, #table do
string = string_gsub(string, table[i], new[i])
end
else
for i=1, #table do
string = string_gsub(string, table[i], new)
end
end
return string
end
table.fexist = function(_table, field)
return _table[field] ~= nil
end
local table_remove = table.remove
table.remove = function(_table, index, onlyfirst)
if type(index) == "number" then
return table_remove(_table, index)
elseif type(index) == "string" then
for k, v in pairs(_table) do
if k == index then
_table[k] = nil -- Can be bugged, probably in future update will be changed with a empty table
if onlyfirst then
return k
end
end
end
else
return table_remove(_table)
end
end
---Check if a table is empty.
---@param t table
---@return boolean
table.empty = function(t)
return next(t) == nil
end
---Internal usage: Inserts a value into a table at a given key, or appends to the end if the key is a number.
---@param t table
---@param k any
---@param v any
local table_insert = function(t, k, v)
if type(k) == "number" then
table.insert(t, v)
else
t[k] = v
end
end
---Merges two tables together, if the same key is found in both tables the second table takes precedence.
---@param t1 table
---@param t2 table
---@return table
table.merge = function(t1, t2)
---@type table
local result = table.clone(t1)
for k, v in pairs(t2) do
table_insert(result, k, v)
end
return result
end
---Checks if the given value exists in the table, if a function is given it test it on each value until it returns true.
---@param t table
---@param value any|fun(value: any): boolean
---@return boolean
table.includes = function(t, value)
if type(value) == "function" then
for _, v in pairs(t) do
if value(v) then
return true
end
end
else
for _, v in pairs(t) do
if value == v then
return true
end
end
end
return false
end
---Filters a table using a given filter, which can be an another table or a function.
---@param t table
---@param filter table|fun(k: any, v: any): boolean
---@return table
table.filter = function(t, filter)
local result = {}
if type(filter) == "function" then
-- returns true.
for k, v in pairs(t) do
if filter(k, v) then
table_insert(result, k, v)
end
end
elseif type(filter) == "table" then
for k, v in pairs(t) do
if table.includes(filter, v) then
table_insert(result, k, v)
end
end
end
return result
end
---Searches a table for the given value and returns the key and value if found.
---@param t table
---@param value any|fun(value: any): boolean
---@return any
table.find = function(t, value)
if type(value) == "function" then
for k, v in pairs(t) do
if value(v) then
return k, v
end
end
else
for k, v in pairs(t) do
if value == v then
return k, v
end
end
end
end
---Returns a table with all keys of the given table.
---@param t table
---@return table
table.keys = function(t)
local keys = {}
for k, _ in pairs(t) do
table.insert(keys, k)
end
return keys
end
---Returns a table with all values of the given table.
---@param t table
---@return table
table.values = function(t)
local values = {}
for _, v in pairs(t) do
table.insert(values, v)
end
return values
end
-- Uses table.clone for fast shallow copying (memcpy) before checking and doing actual deepcopy for nested tables
-- Handles circular references via seen table
-- Significantly faster (~50%) than doing actual deepcopy for flat or lightly-nested structures
---@param orig table
---@return table
table.deepcopy = function(orig, seen)
if type(orig) ~= "table" then return orig end
seen = seen or {}
if seen[orig] then return seen[orig] end
local copy = table.clone(orig)
seen[orig] = copy
for k, v in next, orig do
if type(v) == "table" then
copy[k] = table.deepcopy(v, seen)
end
end
return copy
end
math.round = function(number, decimals)
local _ = 10 ^ decimals
return math.floor((number * _) + 0.5) / (_)
end
--// Dialog //--
local function DialogueTable(entity, dialog, editing)
return {
Question = function(...)
dialog.questions = {...}
return DialogueTable(entity, dialog, editing)
end,
Response = function(...)
local responses = {...}
local formatted_text = {}
local no_formatted = {}
for k1,v1 in pairs(responses) do
no_formatted[k1] = {}
for k,v in pairs(v1) do
if formatted_text[k1] == nil then
formatted_text[k1] = ""
end
formatted_text[k1] = formatted_text[k1]..k.."~w~ "..v.." | "
k = string.multigsub(k, {"%[", "%]"}, {"", ""})
k = string.multigsub(k, {"~r~", "~b~", "~g~", "~y~", "~p~", "~o~", "~c~", "~m~", "~u~", "~n~", "~s~", "~w~"}, {"", "","", "","", "","", "","", "","", ""})
--print("k = "..k)
no_formatted[k1][k] = v
end
formatted_text[k1] = formatted_text[k1]:sub(1, -3)
end
dialog.response = {
no_formatted = no_formatted,
formatted = formatted_text
}
if editing then
_TriggerEvent("Utility:Remove", "Dialogue", entity)
_TriggerEvent("Utility:Create", "Dialogue", entity, dialog)
else
_TriggerEvent("Utility:Create", "Dialogue", entity, dialog)
end
Utility.Cache.Dialogue[entity] = dialog
return DialogueTable(entity, dialog, editing)
end,
LastQuestion = function(last)
Utility.Cache.Dialogue[entity].lastQuestion = last
_TriggerEvent("Utility:Edit", "Dialogue", entity, "lastQuestion", last)
return DialogueTable(entity, dialog, editing)
end
}
end
StartDialogue = function(entity, distance, callback, stopWhenTalking)
local dialog = {
entity = entity,
distance = distance,
curQuestion = 1,
callback = callback,
stopWhenTalking = stopWhenTalking,
slice = GetEntitySlice(entity)
}
developer("^2Created^0", "dialogue with entity", entity)
return DialogueTable(entity, dialog)
end
EditDialogue = function(entity)
if entity ~= nil and IsEntityOnDialogue(entity) then
return DialogueTable(entity, Utility.Cache.Dialogue[entity], true)
end
end
StopDialogue = function(entity, immediately)
if entity ~= nil and IsEntityOnDialogue(entity) then
developer("^1Stopping^0", "dialogue", entity)
-- Set the current question as if it were the last one
if immediately then
Utility.Cache.Dialogue[entity] = nil
else
local questions = Utility.Cache.Dialogue[entity].questions[1]
_TriggerEvent("Utility:Edit", "Dialogue", entity, "curQuestion", #questions)
end
end
end
IsEntityOnDialogue = function(entity)
return Utility.Cache.Dialogue[entity]
end
RegisterNetEvent("Utility:DeleteDialogue", function(entity)
Utility.Cache.Dialogue[entity] = nil
end)
--// N3d //--
function GetScaleformsStatus()
local activeList = {}
local inactiveList = {}
for i = 1, 10 do
local scaleformName = "utility_lib_" .. i
if IsScaleformTaken(scaleformName) then
table.insert(activeList, {name = scaleformName, data = Utility.Cache.N3d[scaleformName]})
else
table.insert(inactiveList, {name = scaleformName, data = {txd = false, show = false, rotation = {}}})
end
end
return activeList, inactiveList
end
function IsScaleformTaken(scaleformName)
return Utility.Cache.N3d[scaleformName] ~= nil
end
local old_RequestScaleformMovie = RequestScaleformMovie
local function RequestScaleformMovie(scaleform)-- idk why but sometimes give error
--print(scaleform)
local status, retval = pcall(old_RequestScaleformMovie, scaleform)
while not status do
status, retval = pcall(old_RequestScaleformMovie, scaleform)
Citizen.Wait(1)
end
return retval
end
local function LoadScaleform(N3dHandle, scaleformName)
local scaleformHandle = RequestScaleformMovie(scaleformName) -- idk why but sometimes give error
-- Wait till it has loaded
local startTimer = GetGameTimer()
while not HasScaleformMovieLoaded(scaleformHandle) and (GetGameTimer() - startTimer) < 4000 do
Citizen.Wait(0)
end
if (GetGameTimer() - startTimer) > 4000 then
developer("^1Error^0", "After 4000 ms to load the scaleform the scaleform has not loaded yet, try again or check that it has started correctly!")
return
end
-- Save the handle in the table
Utility.Cache.N3d[N3dHandle].scaleform = scaleformHandle
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "scaleform", scaleformHandle)
end
local function StartupDui(N3dHandle, url, width, height)
local txd = CreateRuntimeTxd('txd'..N3dHandle) -- Create texture dictionary
Utility.Cache.N3d[N3dHandle].dui = CreateDui(url, width, height) -- Create Dui with the url
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "dui", Utility.Cache.N3d[N3dHandle].dui)
while not IsDuiAvailable(Utility.Cache.N3d[N3dHandle].dui) do
Citizen.Wait(1)
end
local dui = GetDuiHandle(Utility.Cache.N3d[N3dHandle].dui) -- Getting dui handle
CreateRuntimeTextureFromDuiHandle(txd, 'txn'..N3dHandle, dui) -- Applying the txd on the dui
if Utility.Cache.N3d[N3dHandle].scaleform ~= nil and not Utility.Cache.N3d[N3dHandle].txd then
BeginScaleformMovieMethod(Utility.Cache.N3d[N3dHandle].scaleform, 'SET_TEXTURE')
ScaleformMovieMethodAddParamTextureNameString('txd'..N3dHandle) -- txd
ScaleformMovieMethodAddParamTextureNameString('txn'..N3dHandle) -- txn
ScaleformMovieMethodAddParamInt(0) -- x
ScaleformMovieMethodAddParamInt(0) -- y
ScaleformMovieMethodAddParamInt(width)
ScaleformMovieMethodAddParamInt(height)
EndScaleformMovieMethod()
Utility.Cache.N3d[N3dHandle].txd = true
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "txd", true)
end
end
-- Class and handle
function CreateNui3d(scaleformName, url)
local N3dHandle = tostring(math.random(0, 9999))
local _N3d = {
txd = false,
show = false,
rotation = {}
}
Utility.Cache.N3d[N3dHandle] = _N3d
_TriggerEvent("Utility:Create", "N3d", N3dHandle, _N3d) -- Sync the table in the utility_lib
-- Auto load the scaleform
LoadScaleform(N3dHandle, scaleformName)
if url ~= nil then
developer("^2Starting^0", N3dHandle.." with url ".."nui://"..GetCurrentResourceName().."/"..url.." sf "..scaleformName)
StartupDui(N3dHandle, "nui://"..GetCurrentResourceName().."/"..url, 1920, 1080)
end
-- Class to return
local N3d_Class = {}
N3d_Class.__index = N3d_Class
N3d_Class.init = function(self, url, width, height)
StartupDui(N3dHandle, "nui://"..GetCurrentResourceName().."/"..url, width or 1920, height or 1080)
end
N3d_Class.datas = function(self)
return Utility.Cache.N3d[N3dHandle]
end
N3d_Class.scale = function(self, scale)
Utility.Cache.N3d[N3dHandle].advanced_scale = scale
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "advanced_scale", scale)
end
N3d_Class.rotation = function(self, rotation, syncedwithplayer)
Utility.Cache.N3d[N3dHandle].rotation.rotation = rotation
Utility.Cache.N3d[N3dHandle].rotation.syncedwithplayer = syncedwithplayer
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "rotation", {
rotation = rotation,
syncedwithplayer = syncedwithplayer
})
end
N3d_Class.destroy = function(self)
if Utility.Cache.N3d[N3dHandle].dui ~= nil then
DestroyDui(Utility.Cache.N3d[N3dHandle].dui)
SetScaleformMovieAsNoLongerNeeded(Utility.Cache.N3d[N3dHandle].scaleform)
Utility.Cache.N3d[N3dHandle] = nil
_TriggerEvent("Utility:Remove", "N3d", N3dHandle)
end
end
N3d_Class.started = function()
return Utility.Cache.N3d[N3dHandle].dui ~= nil
end
N3d_Class.show = function(self, coords, scale)
Utility.Cache.N3d[N3dHandle].coords = coords
Utility.Cache.N3d[N3dHandle].scale = scale or 0.1
Utility.Cache.N3d[N3dHandle].show = true
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "coords", coords)
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "scale", scale or 0.1)
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "show", true)
end
N3d_Class.visible = function()
return Utility.Cache.N3d[N3dHandle].show
end
N3d_Class.hide = function()
Utility.Cache.N3d[N3dHandle].show = false
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "show", false)
end
N3d_Class.attach = function(self, entity, offset)
Utility.Cache.N3d[N3dHandle].attach = {
entity = entity,
offset = offset or vector3(0.0, 0.0, 0.0)
}
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "attach", {
entity = entity,
offset = offset or vector3(0.0, 0.0, 0.0)
})
end
N3d_Class.detach = function(self, atcoords)
if atcoords then
Utility.Cache.N3d[N3dHandle].coords = GetEntityCoords(Utility.Cache.N3d[N3dHandle].attach.entity)
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "coords", Utility.Cache.N3d[N3dHandle].coords)
end
Utility.Cache.N3d[N3dHandle].attach = nil
_TriggerEvent("Utility:Edit", "N3d", N3dHandle, "attach", nil)
end
N3d_Class.object = function()
return Utility.Cache.N3d[N3dHandle].dui
end
N3d_Class.msg = function(self, msg)
if self:started() then
SendDuiMessage(self:object(), json.encode(msg))
end
end
N3d_Class.replaceTexture = function(self, dict, textureName, wait)
if wait then
Citizen.Wait(wait)
end
AddReplaceTexture(dict, textureName, 'txd'..N3dHandle, 'txn'..N3dHandle)
end
return setmetatable({}, N3d_Class), N3dHandle
end
AddEventHandler("onResourceStop", function(_resName)
if _resName == resName then
for i=1, #Utility.Cache.Events do
RemoveEventHandler(Utility.Cache.Events[i])
end
for handle,data in pairs(Utility.Cache.N3d) do
if data.dui ~= nil and IsDuiAvailable(data.dui) then
DestroyDui(data.dui)
end
_TriggerEvent("Utility:Remove", "N3d", handle)
end
if Utility.Cache.Settings.UseDelete then
for i=1, #Utility.Cache.EntityStack do
local ent = Utility.Cache.EntityStack[i]
NetworkRequestControlOfEntity(ent)
if DoesEntityExist(ent) then
DeleteEntity(ent)
end
end
end
end
end)
--// Animated Object Translations [Test] //--
-- Thanks to https://github.com/gre/bezier-easing for the incredible math behind this, i just converted the code to lua and did the NEWTON_MIN_SLOPE tweening, since precision rounding in lua seems to be different than in js.
-- by Gaëtan Renaudeau 2014 - 2015 MIT License
local NEWTON_ITERATIONS = 10
local NEWTON_MIN_SLOPE = 0.01
local SUBDIVISION_PRECISION = 0.0000001
local SUBDIVISION_MAX_ITERATIONS = 10
local kSplineTableSize = 11
local kSampleStepSize = 1.0 / (kSplineTableSize - 1.0)
local function A(aA1, aA2)
return 1.0 - 3.0 * aA2 + 3.0 * aA1
end
local function B(aA1, aA2)
return 3.0 * aA2 - 6.0 * aA1
end
local function C(aA1)
return 3.0 * aA1
end
-- Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
local function calcBezier(aT, aA1, aA2)
return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT
end
-- Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
local function getSlope(aT, aA1, aA2)
return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1)
end
local function binarySubdivide(aX, aA, aB, mX1, mX2)
local currentX, currentT, i = 0, 0, 0
repeat
currentT = aA + (aB - aA) / 2.0
currentX = calcBezier(currentT, mX1, mX2) - aX
if currentX > 0.0 then
aB = currentT
else
aA = currentT
end
i = i + 1
until math.abs(currentX) <= SUBDIVISION_PRECISION or i >= SUBDIVISION_MAX_ITERATIONS
return currentT
end
local function newtonRaphsonIterate(aX, aGuessT, mX1, mX2)
for i = 1, NEWTON_ITERATIONS do
local currentSlope = getSlope(aGuessT, mX1, mX2)
if currentSlope == 0.0 then
return aGuessT
end
local currentX = calcBezier(aGuessT, mX1, mX2) - aX
aGuessT = aGuessT - currentX / currentSlope
end
return aGuessT
end
local function LinearEasing(x)
return x
end
local function bezier(mX1, mY1, mX2, mY2)
if not (0 <= mX1 and mX1 <= 1 and 0 <= mX2 and mX2 <= 1) then
error('bezier x values must be in [0, 1] range')
end
if mX1 == mY1 and mX2 == mY2 then
return LinearEasing
end
-- Precompute samples table
local sampleValues = {}
for i = 1, kSplineTableSize do
sampleValues[i] = calcBezier((i - 1) * kSampleStepSize, mX1, mX2)
end
local function getTForX(aX)
local intervalStart = 0.0
local currentSample = 1
local lastSample = kSplineTableSize - 1
while currentSample ~= lastSample and sampleValues[currentSample] <= aX do
intervalStart = intervalStart + kSampleStepSize
currentSample = currentSample + 1
end
currentSample = currentSample - 1
-- Interpolate to provide an initial guess for t
local dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample])
local guessForT = intervalStart + dist * kSampleStepSize
local initialSlope = getSlope(guessForT, mX1, mX2)
if initialSlope >= NEWTON_MIN_SLOPE then
return newtonRaphsonIterate(aX, guessForT, mX1, mX2)
elseif initialSlope == 0.0 then
return guessForT
else
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2)
end
end
return function(x)
if x == 0 or x == 1 then
return x
end
return calcBezier(getTForX(x), mY1, mY2)
end
end
local predefinedCubicBeziers = {
ease = {0.25, 0.1, 0.25, 1},
easeIn = {0.42, 0, 1, 1},
easeOut = {0, 0, 0.58, 1},
easeInOut = {0.42, 0, 0.58, 1},
}
TranslateObjectCoordsCubicBezier = function(obj, destination, duration, cubicBezier)
local startCoords = GetEntityCoords(obj)
local startTimer = GetNetworkTimeAccurate()
local timer = GetNetworkTimeAccurate()
local done = false
local space = destination - startCoords
local axis = {"x", "y", "z"}
if type(cubicBezier) == "string" then
cubicBezier = predefinedCubicBeziers[cubicBezier]
if not cubicBezier then
error("TranslateObjectCoordsCubicBezier: `"..cubicBezier.."` its not a predefined cubic bezier")
return
end
end
local easingFunction = bezier(table.unpack(cubicBezier))
while not done and (timer - startTimer) < duration do
Citizen.Wait(0) -- Wait 1 tick
local timeAccurate = GetNetworkTimeAccurate()
if timer ~= 0 and (timeAccurate - timer) ~= 0 then -- If some time has elapsed since the last call
local speed = {}
local progress = (timeAccurate - startTimer) / (duration)
for k, v in pairs(axis) do
if startCoords[v] == destination[v] then
speed[v] = 0
else
local updatedCoords = GetEntityCoords(obj)
local newCoord = math.lerp(startCoords[v], destination[v], easingFunction(progress)) -- we get the new coords from lerp and the easing function for the translation progress
local distance = math.abs(updatedCoords[v] - newCoord)
speed[v] = distance
end
end
done = SlideObject(obj, destination, speed.x, speed.y, speed.z, false)
end
timer = timeAccurate
end
SetEntityCoords(obj, destination)
end
TranslateObjectCoords = function(obj, destination, duration)
TranslateObjectCoordsCubicBezier(obj, destination, duration, {0.1, 0.1, 0.1, 0.1})
end
TranslateUniformRectilinearMotion = TranslateObjectCoords
TranslateObjectRotationCubicBezier = function(obj, destination, duration, rotationOrder, cubicBezier)
local startRotation = GetEntityRotation(obj)
local startTimer = GetNetworkTimeAccurate()
local timer = GetNetworkTimeAccurate()
local axis = {"x", "y", "z"}
local deltaRotation = {}
-- Normalize destination to [-180, 180] range and compute shortest angular path
for _, v in ipairs(axis) do
local start = startRotation[v]
local dest = destination[v]
local delta = ((dest - start + 180) % 360) - 180
deltaRotation[v] = delta
end
if type(cubicBezier) == "string" then
cubicBezier = predefinedCubicBeziers[cubicBezier]
if not cubicBezier then
error("TranslateObjectCoordsCubicBezier: `"..cubicBezier.."` its not a predefined cubic bezier")
return
end
end
local easingFunction = bezier(table.unpack(cubicBezier))
while (timer - startTimer) < duration do
Citizen.Wait(0) -- Wait 1 tick
local timeAccurate = GetNetworkTimeAccurate()
local progress = (timeAccurate - startTimer) / (duration)
if timer ~= 0 and (timeAccurate - timer) ~= 0 then -- If some time has elapsed since the last call
local rot = {}
for k, v in ipairs(axis) do
local start = startRotation[v]
local delta = deltaRotation[v]
rot[v] = start + delta * easingFunction(progress)
end
SetEntityRotation(obj, rot.x, rot.y, rot.z, rotationOrder or 2)
end
timer = timeAccurate
end
SetEntityRotation(obj, destination.x, destination.y, destination.z, rotationOrder or 2)
end
TranslateObjectRotation = function(obj, destination, duration, rotationOrder)
TranslateObjectRotationCubicBezier(obj, destination, duration, rotationOrder, {0.1, 0.1, 0.1, 0.1})
end
--// Heists //--
-- Scene
CreateScene = function(coords, rot, holdLastFrame, looped, animSpeed, animTime)
local scene = NetworkCreateSynchronisedScene(coords, rot, 2, holdLastFrame or false, looped or false, 1065353216, animTime or 0, animSpeed or 1.3)
Utility.Cache.Scenes[scene] = {
coords = coords,
rotation = rot,
players = {},
entities = {},
dicts = {},
}
return scene
end
AddEntityToScene = function(entity, scene, dict, name, speed, speedMultiplier, flag)
if not DoesEntityExist(tonumber(entity)) then
local model = entity
local coords = GetEntityCoords(PlayerPedId())
entity = CreateObject(entity, coords + vector3(0,0, -4.0), true)
SetEntityCollision(entity, false, true)
Utility.Cache.Scenes[scene].entities[model] = entity -- if the entity was created by the scene then it will have automatic handling, otherwise you will have to delete it yourself manually
end
RequestAnimDict(dict)
while not HasAnimDictLoaded(dict) do
Citizen.Wait(1)
end
developer("^3Scenes^0", "Adding object", entity, "to scene", scene, "[", dict, name, "]")
NetworkAddEntityToSynchronisedScene(entity, scene, dict, name, speed or 4.0, speedMultiplier or -8.0, flag or 1)
table.insert(Utility.Cache.Scenes[scene].dicts, dict)
end
AddPlayerToScene = function(player, scene, dict, name, ...)
Citizen.InvokeNative(0x144da052257ae7d8, true) -- synchronize the scene with any player that is in the scene
local ped = DoesEntityExist(player) and player or GetPlayerPed(player) -- (player id) or (player ped id) are accepted
AddPedToScene(ped, scene, dict, name, ...)
Utility.Cache.Scenes[scene].players[ped] = {
dict = dict,
name = name
}
end
AddPedToScene = function(ped, scene, dict, name, blendIn, blendOut, duration, flag)
if not DoesEntityExist(ped) then
local model = ped
ped = CreatePed(ped, vector3(0,0,0), 0.0, true)
Utility.Cache.Scenes[scene].entities[model] = ped -- if the entity was created by the scene then it will have automatic handling, otherwise you will have to delete it yourself manually
end
RequestAnimDict(dict)
while not HasAnimDictLoaded(dict) do
Citizen.Wait(1)
end
developer("^3Scenes^0", "Adding ped", ped, "to scene", scene, "[", dict, name, "]")
NetworkAddPedToSynchronisedScene(ped, scene, dict, name, blendIn or 1.5, blendOut or -4.0, duration or 1, flag or 16, 0, 0)
table.insert(Utility.Cache.Scenes[scene].dicts, dict)
end
GoNearInitialOffset = function(player, coords, rot, dict, name)
-- Taken from https://github.com/root-cause/v-decompiled-scripts/blob/master/fm_mission_controller.c line 752898
local ped = DoesEntityExist(player) and player or GetPlayerPed(player) -- (player id) or (player ped id) are accepted
local heading = rot and rot.z or GetEntityHeading(ped)
--Citizen.Wait(5000)
RequestAnimDict(dict)
while not HasAnimDictLoaded(dict) do
Citizen.Wait(1)
end
local pos = GetAnimInitialOffsetPosition(dict, name, coords, 0.0, 0.0, heading, 0.0, 2)
local rot = GetAnimInitialOffsetRotation(dict, name, coords, 0.0, 0.0, heading, 0.0, 2)
RemoveAnimDict(dict)
TaskGoStraightToCoord(ped, pos, 0.6, -1, rot.z, 0.4)
--TaskFollowNavMeshToCoord(ped, pos, 0.6, -1, 0.1, true)
-- Wait until it is close to the start zone
local startCheckingDistance = GetGameTimer()
--DebugCoords(coords)
--DebugCoords(pos)
while (#(GetEntityCoords(ped) - pos) > 0.3) and (GetGameTimer() - startCheckingDistance) < 4000 do
Citizen.Wait(1)
end
--TaskAchieveHeading(ped, rot.z, 1000)
--Citizen.Wait(1000)
-- Wait until he has stopped
while GetEntitySpeed(ped) > 0.2 and (GetGameTimer() - startCheckingDistance) < 4000 do
Citizen.Wait(50)
end
-- Let's add a break just in case (weird bugs can happen without it)
--Citizen.Wait(1000)
if (GetGameTimer() - startCheckingDistance) >= 4000 then
TaskPedSlideToCoord(ped, pos, heading, 2000)
Citizen.Wait(2000)
end
end
StartScene = function(scene, goNearInitialOffset)
local curScene = Utility.Cache.Scenes[scene]
if goNearInitialOffset then
for ped, v in pairs(curScene.players) do
GoNearInitialOffset(ped, curScene.coords, curScene.rotation, v.dict, v.name)
end
end
NetworkStartSynchronisedScene(scene)
end
StopScene = function(scene)
developer("^3Scenes^0", "Stop scene", scene)
NetworkStopSynchronisedScene(scene)
local curScene = Utility.Cache.Scenes[scene]
-- Delete create entities
for model, entity in pairs(curScene.entities) do
developer("^3Scenes^0", "Deleting entity", entity)
DeleteEntity(entity)
end
-- Unload anim dicts
for i=1, #curScene.dicts do
RemoveAnimDict(curScene.dicts[i])
end
end
GetSceneEntity = function(scene, model)
if model then
return Utility.Cache.Scenes[scene].entities[model]
else
return Utility.Cache.Scenes[scene].entities
end
end
-- Thermal Charge
local StartPlantThermalChargeScene = function(door, coords)
local ped = PlayerPedId()
local rot = GetEntityRotation(door)
--DebugCoords(coords)
--GoNearInitialOffset(ped, coords, "anim@heists@ornate_bank@thermal_charge", "thermal_charge")
local scene = CreateScene(coords, rot)
AddPlayerToScene(ped, scene, "anim@heists@ornate_bank@thermal_charge", "thermal_charge")
AddEntityToScene("hei_p_m_bag_var22_arm_s", scene, "anim@heists@ornate_bank@thermal_charge", "bag_thermal_charge")
StartScene(scene, true)
return scene
end
local FindDoorLockCoords = function(door)
local size = GetEntitySize(door)
if doorHash == GetHashKey("hei_v_ilev_bk_safegate_pris") then
-- SafePedCoords
return GetOffsetFromEntityInWorldCoords(door, -(size.x - 0.1), -0.05, 0.0)
else
-- SafePedCoords
return GetOffsetFromEntityInWorldCoords(door, (size.x - 0.1), -0.05, 0.0)
end
end
local PullOutThermalCharge = function(ped, coords)
local thermal = CreateObject("hei_prop_heist_thermite", coords - vector3(0, 0, 5), true)
SetEntityCollision(thermal, false, false)
AttachEntityToEntity(thermal, ped, GetPedBoneIndex(ped, 28422), 0, 0, 0, 0, 0, 200.0, true, true, false, true, 1, true)
return thermal
end
local PlantThermalCharge = function(thermal)
DetachEntity(thermal, true, true)
end
local StartThermalChargeEffect = function(thermal)
return StartParticleFxOnNetworkEntity("scr_ornate_heist", "scr_heist_ornate_thermal_burn", thermal, vector3(0.0, 1.0, -0.1), vector3(0.0, 0.0, 0.0), 1.0)
end
local CoverEyesFromThermalCharge = function(ped)
TaskPlayAnim(ped, "anim@heists@ornate_bank@thermal_charge", "cover_eyes_loop", 1.5, 1.0, -1, 51, 1, 0, 0, 0)
end
local GetMoltenModel = function(door)
local model = GetEntityModel(door)
if model == GetHashKey("hei_v_ilev_bk_gate_pris") then
return "hei_v_ilev_bk_gate_molten"
elseif model == GetHashKey("hei_v_ilev_bk_gate2_pris") then
return "hei_v_ilev_bk_gate2_molten"
elseif model == GetHashKey("hei_v_ilev_bk_safegate_pris") then
return "hei_v_ilev_bk_safegate_molten"
end
end
local ChangeDoorModel = function(door)
local moltenModel = GetMoltenModel(door)
if moltenModel then
SetEntityModel(door, moltenModel)
end
end
local StopThermalChargeEffect = function(ped, thermal)
DeleteObject(thermal)
TaskPlayAnim(ped, "anim@heists@ornate_bank@thermal_charge", "cover_eyes_exit", 1.0, 8.0, 1000, 51, 1, 0, 0, 0)
Citizen.Wait(1000)
ClearPedTasks(ped)
end
BreakDoorWithThermalCharge = function(door, bagComponent, duration)
local ped = PlayerPedId()
local doorLock = FindDoorLockCoords(door)
local scene = StartPlantThermalChargeScene(door, doorLock)
SetPedComponentVariation(ped, 5, 0, 0, 0) -- Remove real bag from player
Citizen.Wait(1000)
local thermal = PullOutThermalCharge(ped, doorLock)
Citizen.Wait(3000)
PlantThermalCharge(thermal)
--print("start effect")
Citizen.Wait(1000)
local effect = StartThermalChargeEffect(thermal)
--print("stop scene")
StopScene(scene)
SetPedComponentVariation(ped, 5, bagComponent or 45, 0, 0) -- Reset real bag to player
developer("^3Scenes^0", "Cover eyes")
--print("cover eyes")
CoverEyesFromThermalCharge(ped)
Citizen.Wait(1000)
ChangeDoorModel(door)
developer("^3Scenes^0", "Wait "..(duration or 3000))
Citizen.Wait(duration or 3000)
StopThermalChargeEffect(ped, thermal)
end
-- Trolly
-- Create
local GetTrollyModel = function(type)
if type == "cash" then
return "hei_prop_hei_cash_trolly_01"
elseif type == "gold" then
return "ch_prop_gold_trolly_01a"
elseif type == "diamond" then
return "ch_prop_diamond_trolly_01a"
end
end
local GenerateTrollyId = function(type)
return "utility_heist:"..type.."_trolly:"..math.random(1, 10000) -- example: utility_heist:cash_trolly:3910
end
CreateTrolly = function(type, coords, giveCash, notify, repeatedlyPress, minSpeed, maxSpeed, networked)
if type(repeatedlyPress) == "number" then -- For backwards compatibility
networked = maxSpeed
maxSpeed = minSpeed
minSpeed = repeatedlyPress
end
local obj = nil
local id = GenerateTrollyId(type) -- Pseudo random id
-- Object creation
if type == "cash" then
obj = CreateObject("hei_prop_hei_cash_trolly_01", coords, networked)
elseif type == "gold" then
obj = CreateObject("ch_prop_gold_trolly_01a", coords, networked)
elseif type == "diamond" then
obj = CreateObject("ch_prop_diamond_trolly_01a", coords, networked)
end
PlaceObjectOnGroundProperly(obj)
-- Marker and data creation
CreateMarker(id, coords, 0.0, 2.0, {notify = notify or "Press {E} to begin looting the trolly"})
SetFor(id, "minSpeed", minSpeed)
SetFor(id, "maxSpeed", maxSpeed)
SetFor(id, "giveCash", giveCash)
SetFor(id, "repeatedlyPress", repeatedlyPress)
local eventHandler = nil
eventHandler = On("marker", function(_id)
if _id == id then
local type = id:match("utility_heist:(%w+)_trolly")
local coords = GetEntityCoords(PlayerPedId())
local model = GetTrollyModel(type)
local trollyObj = GetClosestObjectOfType(coords, 3.0, GetHashKey(model))
DeleteMarker(id)
Citizen.Wait(500)
ClearAllHelpMessages()
if trollyObj > 0 then
LootTrolly(id, type, trollyObj)
RemoveEventHandler(eventHandler)
end
end
end)
return id, obj
end
-- Loot
local GetEmptyTrollyModel = function(type)
if type == "cash" then
return "hei_prop_hei_cash_trolly_03"
else
return "hei_prop_hei_cash_trolly_03"
--return "ch_prop_gold_trolly_empty"
end
end
local GetTrollyCashProp = function(type)
if type == "cash" then
return "hei_prop_heist_cash_pile"
elseif type == "gold" then
return "ch_prop_gold_bar_01a"
elseif type == "diamond" then
return "ch_prop_vault_dimaondbox_01a"
end
end
local CollectCashProp = function(id, giveCash)
PlaySoundFrontend(-1, "LOCAL_PLYR_CASH_COUNTER_INCREASE", "DLC_HEISTS_GENERAL_FRONTEND_SOUNDS", true)
giveCash() -- Give cash function
end
local CreateCashProp = function(id, model, giveCash)
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local cashProp = CreateObject(model, coords, true)
FreezeEntityPosition(cashProp, true)
SetEntityInvincible(cashProp, true)
SetEntityNoCollisionEntity(cashProp, ped)
SetEntityVisible(cashProp, false, false)
AttachEntityToEntity(cashProp, ped, GetPedBoneIndex(ped, 60309), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false, false, false, false, 0, true)
DisableCamCollisionForEntity(cashProp)
Utility.Cache.LootingTrolly = true
Citizen.CreateThread(function()
local eventCashAppear = GetHashKey("CASH_APPEAR")
local eventReleaseCashDestroy = GetHashKey("RELEASE_CASH_DESTROY")
while Utility.Cache.LootingTrolly do
if HasAnimEventFired(ped, eventCashAppear) then
SetEntityVisible(cashProp, true, false) -- Set entity visible
end
if HasAnimEventFired(ped, eventReleaseCashDestroy) then
if IsEntityVisible(cashProp) then -- Set Entity invisible
SetEntityVisible(cashProp, false, false)
CollectCashProp(id, giveCash)
end
end
Citizen.Wait(1)
end
DeleteObject(cashProp)
end)
end
local StartLootIntroScene = function(bag, trolly)
local ped = PlayerPedId()
local coords = GetEntityCoords(trolly)
local rot = GetEntityRotation(trolly)
local scene = CreateScene(coords, rot)
AddPlayerToScene(ped, scene, "anim@heists@ornate_bank@grab_cash", "intro")
AddEntityToScene(bag, scene, "anim@heists@ornate_bank@grab_cash", "bag_intro")
StartScene(scene, true)
return scene
end
local StartPlayerInteractionGrabLoop = function(grabScene, min, max, repeatedlyPress)
local lscene = NetworkGetLocalSceneFromNetworkId(grabScene)
local speed = min
local finished = false
-- Every mouse click add 0.1 to the speed
Citizen.CreateThread(function()
while not finished do
if IsControlJustPressed(0, 24) then
if speed <= max then
speed = speed + 0.1
end
end
Citizen.Wait(0)
end
end)
-- Wait that the scene start
while not IsSynchronizedSceneRunning(lscene) do
lscene = NetworkGetLocalSceneFromNetworkId(grabScene)
Citizen.Wait(1)
end
Citizen.CreateThread(function()
AddTextEntry('PersistentButtonNotification', repeatedlyPress or "Repeatedly press ~INPUT_SCRIPT_RDOWN~ to grab faster")
BeginTextCommandDisplayHelp('PersistentButtonNotification')
EndTextCommandDisplayHelp(0, true, true, -1)
end)
-- If the scene is still running, remove 0.1 every 300ms
while GetSynchronizedScenePhase(lscene) < 0.99 do
lscene = NetworkGetLocalSceneFromNetworkId(grabScene)
if speed > min then
speed = speed - 0.1
end
SetSynchronizedSceneRate(lscene, speed)
Citizen.Wait(300)
end
ClearAllHelpMessages()
finished = true
--print("Finished grabbing money")
end
local StartLootGrabScene = function(bag, trolly)
local ped = PlayerPedId()
local coords = GetEntityCoords(trolly)
local rot = GetEntityRotation(trolly)
local scene = CreateScene(coords, rot)
AddPlayerToScene(ped, scene, "anim@heists@ornate_bank@grab_cash", "grab")
AddEntityToScene(bag, scene, "anim@heists@ornate_bank@grab_cash", "bag_grab")
AddEntityToScene(trolly, scene, "anim@heists@ornate_bank@grab_cash", "cart_cash_dissapear")
StartScene(scene)
return scene
end
local StartLootExitScene = function(bag, trolly)
local ped = PlayerPedId()
local coords = GetEntityCoords(trolly)
local rot = GetEntityRotation(trolly)
local scene = CreateScene(coords, rot)
AddPlayerToScene(ped, scene, "anim@heists@ornate_bank@grab_cash", "exit")
AddEntityToScene(bag, scene, "anim@heists@ornate_bank@grab_cash", "bag_exit")
StartScene(scene)
return scene
end
LootTrolly = function(id, type, trolly)
local ped = PlayerPedId()
local cashPropModel = GetTrollyCashProp(type)
local emptyTrolly = GetEmptyTrollyModel(type)
local options = GetFrom(id)
if IsEntityPlayingAnim(trolly, "anim@heists@ornate_bank@grab_cash", "cart_cash_dissapear", 3) then
return
end
while not NetworkHasControlOfEntity(trolly) do
Citizen.Wait(1)
NetworkRequestControlOfEntity(trolly)
end
local playerCoords = GetEntityCoords(ped)
local bagObj = CreateObject("hei_p_m_bag_var22_arm_s", playerCoords + vector3(0.0, 0.0, -6.0), true)
SetEntityCollision(bagObj, false, true)
-- Intro
local introScene = StartLootIntroScene(bagObj, trolly)
developer("^3Scenes^0", "Started Intro scene")
SetPedComponentVariation(ped, 5, 0, 0, 0)
Citizen.Wait(1500)
developer("^3Scenes^0", "Create cash prop")
CreateCashProp(id, cashPropModel, options.giveCash)
developer("^3Scenes^0", "Starting grabbing scene")
-- Grab Scene
local grabScene = StartLootGrabScene(bagObj, trolly)
developer("^3Scenes^0", "Started grab scene")
StartPlayerInteractionGrabLoop(grabScene, options.minSpeed or 1.0, options.maxSpeed or 1.6, options.repeatedlyPress)
CollectCashProp(id, options.giveCash) -- last cash prop isnt in the animation events
SetEntityModel(trolly, emptyTrolly)
-- Exit
Utility.Cache.LootingTrolly = false
local exitScene = StartLootExitScene(bagObj, trolly)
developer("^3Scenes^0", "Started exit scene", trolly)
Citizen.Wait(1800)
DeleteEntity(bagObj)
StopScene(introScene)
StopScene(grabScene)
StopScene(exitScene)
developer("^3Scenes^0", "Stopped all scenes", trolly)
SetPedComponentVariation(ped, 5, 45, 0, 0)
end
-- Guards
local GuardAlertnessLoopRunning = false
local SpottedByGuards = false
Citizen.CreateThread(function()
AddRelationshipGroup("GUARDS")
SetPedRelationshipGroupHash(PlayerPedId(), GetHashKey("PLAYER"))
end)
local CheckIfCanAttack = function(player, v)
if HasEntityClearLosToEntity(v, player, 27) and GetPedTaskCombatTarget(v) ~= player then
TaskCombatHatedTargetsAroundPed(v, 10.0, 0)
SetRelationshipBetweenGroups(5, GetHashKey("GUARDS"), GetHashKey("PLAYER"))
SetPedToInformRespectedFriends(v, 30.0, 3)
SetPedAiBlipHasCone(v, false)
if not SpottedByGuards then
SpottedByGuards = true
TriggerEvent("Utility:On:spotted", v)
end
end
end
local TryToStartGuardAlertnessLoop = function()
if not GuardAlertnessLoopRunning then
GuardAlertnessLoopRunning = true
Citizen.CreateThread(function()
while GuardAlertnessLoopRunning do
if next(Utility.Cache.Guards) then -- if there's any guard
local player = PlayerPedId()
local coords = GetEntityCoords(player)
local inStealth = GetPedStealthMovement(player)
local distance = inStealth and 30.0 or 60.0 -- (if stealth then 30.0 else 60.0)
local running = IsPedRunning(player)
for k,v in ipairs(Utility.Cache.Guards) do
local guardCoords = GetEntityCoords(v)
-- Check if is dying
if IsPedDeadOrDying(v) then
SetPedCanRagdoll(v, true)
SetEntityAsNoLongerNeeded(v)
table.remove(Utility.Cache.Guards, k)
else
-- Check if to near
if #(coords - guardCoords) < (running and 8.0 or 5.0) then -- if to near
CheckIfCanAttack(player, v)
end
-- Check if can be viewed
if #(coords - guardCoords) < distance then -- if is in the possible cone
local guardMaxCoords = GetOffsetFromEntityInWorldCoords(v, 0.0, distance, 0.0)
if IsEntityInAngledArea(PlayerPedId(), guardCoords, guardMaxCoords, 50.0) then
CheckIfCanAttack(player, v)
end
end
if IsPedShooting(v) or IsPedInCombat(v) then
SetPedToInformRespectedFriends(v, 30.0, 3)
SetPedAiBlipHasCone(v, false)
if not SpottedByGuards then
SpottedByGuards = true
TriggerEvent("Utility:On:spotted", v)
end
end
end
end
else
SpottedByGuards = false
GuardAlertnessLoopRunning = false
end
Citizen.Wait(500)
end
end)
end
end
SetGuardDifficulty = function(guard, difficulty)
local armour, alertness, accuracy, range, ability = 0, 0, 0, 0, 0
if difficulty == "easy" then
alertness = 1
accuracy = 40
range = 0
ability = 0
elseif difficulty == "medium" then
alertness = 2
accuracy = 60
range = 2
ability = 1
elseif difficulty == "hard" then
alertness = 3
accuracy = 80
range = 2
ability = 2
armour = 50
elseif difficulty == "veryhard" then
alertness = 3
accuracy = 95
range = 2
ability = 2
armour = 100
end
SetPedArmour(ped, armour)
SetPedAlertness(ped, alertness)
SetPedAccuracy(ped, accuracy)
SetPedCombatRange(ped, range)
SetPedCombatAbility(ped, ability)
end
CreateGuard = function(model, coords, heading, difficulty, guardRoute)
local ped, netId = CreatePed(model, coords, heading, true)
SetPedAiBlip(ped, true)
SetPedAiBlipForcedOn(ped, true)
SetPedAiBlipHasCone(ped, true)
SetPedRandomComponentVariation(ped, 0)
SetPedRandomProps(ped)
SetPedCanRagdoll(ped, false)
--SetEntityAsMissionEntity(ped)
SetPedCombatMovement(ped, 2)
SetGuardDifficulty(ped, difficulty)
SetPedCombatAttributes(ped, 46, true)
SetPedFleeAttributes(ped, 0, false)
--GiveWeaponToPed(ped, `WEAPON_PISTOL`, 255, false, true)
SetPedRelationshipGroupHash(ped, GetHashKey("GUARDS"))
if guardRoute then
TaskPatrol(ped, "miss_"..guardRoute, 1, 0, 1)
end
table.insert(Utility.Cache.Guards, ped)
TryToStartGuardAlertnessLoop()
return ped
end
CreateGuardRoute = function(name, positions, manualRouteLink)
OpenPatrolRoute("miss_"..name)
local debugLines = {}
for i=1, #positions do
local position = positions[i]
if type(position) == "vector3" then
AddPatrolRouteNode(i, "StandGuard", position, position, 5000)
else
AddPatrolRouteNode(i, position.anim or "StandGuard", position.destination, position.viewat or position.destination, position.wait or 5000)
end
if manualRouteLink then
manualRouteLink(i-1, i)
else
if i == #positions then
AddPatrolRouteLink(i, 1) -- close the circle
table.insert(debugLines, {positions[i], positions[1]})
end
if i > 1 then
AddPatrolRouteLink(i-1, i)
table.insert(debugLines, {positions[i-1], positions[i]})
end
end
end
ClosePatrolRoute()
CreatePatrolRoute()
if DevModeStatus then
Citizen.CreateThread(function()
while true do
for i=1, #debugLines do
DrawLine(debugLines[i][1], debugLines[i][2], 255, 0, 0, 255)
end
Citizen.Wait(0)
end
end)
end
end
SetGuardRoute = function(guard, route)
TaskPatrol(guard, "miss_"..route, 1, 0, 1)
end
--// Other //--
SetEntityModel = function(entity, model)
TriggerServerEvent("Utility:SwapModel", GetEntityCoords(entity), GetEntityModel(entity), type(model) == "string" and GetHashKey(model) or model)
end
StopCurrentTaskAndWatchPlayer = function(ped, duration)
local coords1 = GetEntityCoords(ped, true)
local coords2 = GetEntityCoords(PlayerPedId(), true)
local heading = GetHeadingFromVector_2d(coords2.x - coords1.x, coords2.y - coords1.y)
TaskAchieveHeading(ped, heading, duration or 2000)
end
StartParticleFxOnNetworkEntity = function(ptxAsset, name, obj, ...)
TriggerServerEvent("Utility:StartParticleFxOnNetworkEntity", ptxAsset, name, ObjToNet(obj), ...)
end
GetEntitySize = function(entity)
local model = GetEntityModel(entity)
local min, max = GetModelDimensions(model)
return max - min
end
DebugCoords = function(coords)
Citizen.CreateThread(function()
while true do
DrawText3Ds(coords, "V")
Citizen.Wait(0)
end
end)
end
GetDirectionFromVectors = function(vec, vec2)
return vec - vec2
end
RotationToDirection = function(rotation)
local adjustedRotation =
{
x = (math.pi / 180) * rotation.x,
y = (math.pi / 180) * rotation.y,
z = (math.pi / 180) * rotation.z
}
local direction =
{
x = -math.sin(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)),
y = math.cos(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)),
z = math.sin(adjustedRotation.x)
}
return vector3(direction.x, direction.y, direction.z)
end
SetVehicleWheelsPowered = function(veh, active)
for i=0, GetVehicleNumberOfWheels(veh) - 1 do
SetVehicleWheelIsPowered(veh, i, active)
end
end
apairs = function(t, f)
local a = {}
local i = 0
for k in pairs(t) do table.insert(a, k) end
table.sort(a, f)
local iter = function() -- iterator function
i = i + 1
if a[i] == nil then
return nil
else
return a[i], t[a[i]]
end
end
return iter
end
-- https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/
math.lerp = function(start, _end, perc)
return start + (_end - start) * perc
end
math.invlerp = function(start, _end, value)
return (value - start) / (_end - start)
end
CreateMissionText = function(msg, duration)
SetTextEntry_2("STRING")
AddTextComponentString(msg)
DrawSubtitleTimed(duration and math.floor(duration) or 60000 * 240, 1) -- 4h
return {
delete = function()
ClearPrints()
end
}
end
WaitNear = function(coords)
local player = PlayerPedId()
while #(GetEntityCoords(player) - coords) > 10 do
Citizen.Wait(100)
end
end
FindInTable = function(table, text)
for i=1, #table do
if table[i] == text then
return i
end
end
return nil
end
GetRandom = function(table)
local random = math.random(1, #table)
return table[random]
end
Probability = function(number)
return math.random(1, 100) <= number
end
AddPercentage = function(number, percentage)
return number + (number * percentage / 100)
end
RemovePercentage = function(number, percentage)
return number - (number * percentage / 100)
end
InTimeRange = function(min, max)
local hour = nil
if utc then
local _, _, _, _hour = GetUtcTime()
hour = _hour
else
hour = GetClockHours()
end
if max > min then
if hour >= min and hour <= max then
return true
end
else
-- to fix the times from one day to another, for example from 22 to 3
if hour <= max or hour >= min then
return false
end
end
end
quat2euler = function(q)
-- roll (x-axis rotation)
local sinr_cosp = 2 * (q.w * q.x + q.y * q.z);
local cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y);
local roll = math.atan2(sinr_cosp, cosr_cosp);
-- pitch (y-axis rotation)
local sinp = math.sqrt(1 + 2 * (q.w * q.y - q.x * q.z));
local cosp = math.sqrt(1 - 2 * (q.w * q.y - q.x * q.z));
local pitch = 2 * math.atan2(sinp, cosp) - math.pi / 2;
-- yaw (z-axis rotation)
local siny_cosp = 2 * (q.w * q.z + q.x * q.y);
local cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z);
local yaw = math.atan2(siny_cosp, cosy_cosp);
return vec3(math.deg(roll), math.deg(pitch), math.deg(yaw));
end
GenerateMatrix = function(pos, rot)
local rx, ry, rz = math.rad(rot.x), math.rad(rot.y), math.rad(rot.z)
-- Precompute
local cosX, sinX = math.cos(rx), math.sin(rx)
local cosY, sinY = math.cos(ry), math.sin(ry)
local cosZ, sinZ = math.cos(rz), math.sin(rz)
local mrx = mat3(
vec3(1, 0, 0),
vec3(0, cosX, -sinX),
vec3(0, sinX, cosX)
)
local mry = mat3(
vec3(cosY, 0, sinY),
vec3(0, 1, 0),
vec3(-sinY, 0, cosY)
)
local mrz = mat3(
vec3(cosZ, -sinZ, 0),
vec3(sinZ, cosZ, 0),
vec3(0, 0, 1)
)
local rotationMatrix = mrx * mry * mrz
-- Construct the final transform matrix
local transformMatrix = mat4(
vec4(rotationMatrix[1].x, rotationMatrix[2].x, rotationMatrix[3].x, 0),
vec4(rotationMatrix[1].y, rotationMatrix[2].y, rotationMatrix[3].y, 0),
vec4(rotationMatrix[1].z, rotationMatrix[2].z, rotationMatrix[3].z, 0),
vec4(pos.x, pos.y, pos.z, 1)
)
return transformMatrix
end
GetOffsetFromPositionInWorldCoords = function(pos, rot, offset)
local m = GenerateMatrix(pos, rot)
return m * offset
end
GetInteriorPositionAndRotation = function(interior)
local pos = vec3(GetInteriorPosition(interior))
local rot = vec4(GetInteriorRotation(interior))
rot = quat2euler(rot)
return pos, rot
end
GetOffsetFromInteriorInWorldCoords = function(interior, offset)
local pos, rot = GetInteriorPositionAndRotation(interior)
return GetOffsetFromPositionInWorldCoords(pos, rot, offset)
end
--// UtilityNet //
local CreatedEntities = {}
local old_GetEntityArchetypeName = GetEntityArchetypeName
GetEntityArchetypeName = function(entity)
if not entity or not DoesEntityExist(entity) then
return ""
end
local res = old_GetEntityArchetypeName(entity)
if res == "" then
return Entity(entity)?.state?.abstract_model or res
else
return res
end
end
--#region API
UtilityNet.ForEachEntity = function(fn, slices)
if slices then
for i = 1, #slices do
local _entities = UtilityNet.GetEntities(slices[i])
local n = 0
if _entities then
-- Manual pairs loop for performance
local k,v = next(_entities)
while k do
n = n + 1
local ret = fn(v, k)
if ret ~= nil then
return ret
end
k,v = next(_entities, k)
end
end
end
else
local entities = UtilityNet.GetEntities()
if not entities then
return
end
-- Manual pairs loop for performance
local sliceI,slice = next(entities)
while sliceI do
local k2, v = next(slice)
while k2 do
local ret = fn(v, k2)
if ret ~= nil then
return ret
end
k2,v = next(slice, k2)
end
sliceI, slice = next(entities, sliceI)
end
end
end
UtilityNet.SetDebug = function(state)
UtilityNetDebug = state
local localEntities = {}
Citizen.CreateThread(function()
while UtilityNetDebug do
localEntities = {}
UtilityNet.ForEachEntity(function(v)
if v.createdBy == GetCurrentResourceName() then
local obj = UtilityNet.GetEntityFromUNetId(v.id)
if DoesEntityExist(obj) then
table.insert(localEntities, {
obj = obj,
netId = v.id
})
end
end
end)
Citizen.Wait(3000)
end
end)
Citizen.CreateThread(function()
while UtilityNetDebug do
for k,v in pairs(localEntities) do
local state = UtilityNet.State(v.netId)
if DoesEntityExist(v.obj) then
DrawText3Ds(GetEntityCoords(v.obj), "NetId: "..v.netId, 0.25)
end
end
Citizen.Wait(1)
end
end)
end
UtilityNet.SetModelRenderDistance = function(model, distance)
TriggerServerEvent("Utility:Net:SetModelRenderDistance", model, distance)
end
UtilityNet.CreateEntity = function(model, coords, options)
if type(model) ~= "string" then
error("Invalid model, got "..type(model).." expected string", 0)
end
-- Set resource name in options
options = options or {}
options.createdBy = GetCurrentResourceName()
local callId = math.random(0, 10000000)
local event = nil
local entity = promise:new()
-- Callback
event = RegisterNetEvent("Utility:Net:EntityCreated", function(_callId, object)
if _callId == callId then
entity:resolve(object.id)
RemoveEventHandler(event)
end
end)
TriggerLatentServerEvent("Utility:Net:CreateEntity", 5120, callId, model, coords, options)
local id = Citizen.Await(entity) -- Wait for server response
table.insert(CreatedEntities, id)
return id
end
UtilityNet.DoesEntityExist = function(uNetId)
return DoesEntityExist(UtilityNet.GetEntityFromUNetId(uNetId))
end
UtilityNet.GetClosestRenderedNetIdOfType = function(coords, radius, model)
local entities = exports["utility_lib"]:GetRenderedEntities()
local closest = nil
local minDist = math.huge
for k, v in pairs(entities) do
if DoesEntityExist(v.obj) and GetEntityModel(v.obj) == model then
local dist = #(coords - GetEntityCoords(v.obj))
if dist < minDist then
closest = k
minDist = dist
end
end
end
return closest
end
UtilityNet.GetClosestRenderedObjectOfType = function(coords, radius, model)
local closest = UtilityNet.GetClosestRenderedNetIdOfType(coords, radius, model)
if closest then
return UtilityNet.GetEntityFromUNetId(closest)
end
end
UtilityNet.GetClosestNetIdOfType = function(coords, radius, model)
if type(model) == "string" then
model = GetHashKey(model)
end
local closest = nil
local minDist = math.huge
local slice = GetSliceFromCoords(coords)
local slices = GetSurroundingSlices(slice)
-- Iterate only through near slices to improve performance
for k, v in pairs(slices) do
UtilityNet.ForEachEntity(function(entity)
if entity.model == model then
local distance = #(coords - entity.coords)
if distance < radius and distance < minDist then
minDist = distance
closest = entity.id
end
end
end, {v})
end
return closest
end
UtilityNet.DeleteEntity = function(uNetId)
TriggerServerEvent("Utility:Net:DeleteEntity", uNetId)
for k, v in pairs(CreatedEntities) do
if v == uNetId then
table.remove(CreatedEntities, k)
break
end
end
end
UtilityNet.AttachToEntity = function(uNetId, object, bone, pos, rot, useSoftPinning, collision, rotationOrder, syncRot)
local params = {bone = bone, pos = pos, rot = rot, useSoftPinning = useSoftPinning, collision = collision, rotationOrder = rotationOrder, syncRot = syncRot}
if DoesEntityExist(object) and NetworkGetEntityIsNetworked(object) then
TriggerServerEvent("Utility:Net:AttachToEntity", uNetId, NetworkGetNetworkIdFromEntity(object), params)
else
params.isUtilityNet = true
TriggerServerEvent("Utility:Net:AttachToEntity", uNetId, object, params)
end
end
UtilityNet.DetachEntity = function(uNetId)
local obj = UtilityNet.GetEntityFromUNetId(uNetId)
local coords = GetEntityCoords(obj)
TriggerServerEvent("Utility:Net:DetachEntity", uNetId, coords)
while IsEntityAttached(obj) do
Citizen.Wait(1)
end
local state = UtilityNet.State(uNetId)
while state.__attached do
Citizen.Wait(1)
end
end
-- Using a latent event to prevent blocking the network channel
UtilityNet.SetEntityCoords = function(uNetId, coords, skipPositionUpdate)
TriggerLatentServerEvent("Utility:Net:SetEntityCoords", 5120, uNetId, coords)
-- Instantly sync for local obj
TriggerEvent("Utility:Net:RefreshCoords", uNetId, coords, skipPositionUpdate)
end
UtilityNet.SetEntityRotation = function(uNetId, rot, skipRotationUpdate)
TriggerLatentServerEvent("Utility:Net:SetEntityRotation", 5120, uNetId, rot, skipRotationUpdate)
-- Instantly sync for local obj
TriggerEvent("Utility:Net:RefreshRotation", uNetId, rot, skipRotationUpdate)
end
UtilityNet.SetEntityModel = function(uNetId, model)
TriggerLatentServerEvent("Utility:Net:SetEntityModel", 5120, uNetId, model)
-- Instantly sync for local obj
TriggerEvent("Utility:Net:RefreshModel", uNetId, model)
end
UtilityNet.GetEntityCoords = function(uNetId)
local entity = UtilityNet.InternalFindFromNetId(uNetId)
if entity then
return entity.coords
end
end
UtilityNet.GetEntityRotation = function(uNetId)
local entity = UtilityNet.InternalFindFromNetId(uNetId)
if entity then
return entity.options.rotation
end
end
UtilityNet.PreserveEntity = function(uNetId)
local entity = UtilityNet.GetEntityFromUNetId(uNetId)
if entity then
Entity(entity).state.preserved = true
else
warn("PreserverEntity: Entity with uNetId "..uNetId.." not found")
end
end
UtilityNet.OnRender = function(cb)
AddEventHandler("Utility:Net:OnRender", cb)
end
UtilityNet.OnUnrender = function(cb)
AddEventHandler("Utility:Net:OnUnrender", cb)
end
UtilityNet.IsReady = function(uNetId)
local obj = UtilityNet.GetEntityFromUNetId(uNetId)
return DoesEntityExist(obj) and UtilityNet.IsEntityRendered(obj)
end
UtilityNet.IsEntityRendered = function(obj)
local state = Entity(obj).state
return state.rendered
end
UtilityNet.DoesUNetIdExist = function(uNetId)
local entity = UtilityNet.InternalFindFromNetId(uNetId)
return entity or false
end
--#region State
UtilityNet.AddStateBagChangeHandler = function(uNetId, func)
return RegisterNetEvent("Utility:Net:UpdateStateValue", function(s_uNetId, key, value)
if uNetId == s_uNetId then
func(key, value)
end
end)
end
UtilityNet.RemoveStateBagChangeHandler = function(eventData)
if eventData and eventData.key and eventData.name then
RemoveEventHandler(eventData)
end
end
UtilityNet.State = function(uNetId)
local state = setmetatable({}, {
__index = function(_, k)
return exports["utility_lib"]:GetEntityStateValue(uNetId, k)
end,
__newindex = function(_, k, v)
error("Cannot set states from client")
end
})
return state
end
UtilityNet.StateFromObj = function(obj)
local netId = UtilityNet.GetUNetIdFromEntity(obj)
return UtilityNet.State(netId)
end
--#endregion
--#region Casters
UtilityNet.GetEntityFromUNetId = function(uNetId)
return exports["utility_lib"]:GetEntityFromUNetId(uNetId)
end
UtilityNet.GetUNetIdFromEntity = function(entity)
return exports["utility_lib"]:GetUNetIdFromEntity(entity)
end
UtilityNet.GetuNetIdCreator = function(uNetId)
return exports["utility_lib"]:GetuNetIdCreator(uNetId)
end
UtilityNet.GetEntityCreator = function(entity)
return exports["utility_lib"]:GetEntityCreator(entity)
end
UtilityNet.InternalFindFromNetId = function(uNetId)
return exports["utility_lib"]:InternalFindFromNetId(uNetId)
end
UtilityNet.GetEntities = function(slice)
return exports["utility_lib"]:GetEntities(slice)
end
--#endregion
--#endregion
--#region Garbage Collection
AddEventHandler("onResourceStop", function(resource)
local currentResource = GetCurrentResourceName()
if resource == currentResource then
for k, v in pairs(CreatedEntities) do
TriggerServerEvent("Utility:Net:DeleteEntity", v)
end
end
end)
--#endregion