forked from Simnation/Main
3385 lines
115 KiB
Lua
3385 lines
115 KiB
Lua
_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
|