forked from Simnation/Main
3340 lines
114 KiB
Lua
3340 lines
114 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
|
|||
|
|
|||
|
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 bagObj = CreateObject("hei_p_m_bag_var22_arm_s", vector3(0.0, 0.0, 0.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 = {}
|
|||
|
|
|||
|
--#region API
|
|||
|
UtilityNet.ForEachEntity = function(fn, slices)
|
|||
|
if slices then
|
|||
|
local entities = GlobalState.Entities
|
|||
|
|
|||
|
for i = 1, #slices do
|
|||
|
local _entities = entities[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 = GlobalState.Entities
|
|||
|
|
|||
|
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.InternalFindFromNetId = function(uNetId)
|
|||
|
for sliceI, slice in pairs(GlobalState.Entities) do
|
|||
|
if slice[uNetId] then
|
|||
|
return slice[uNetId], 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)
|
|||
|
|
|||
|
DrawText3Ds(GetEntityCoords(v.obj), "NetId: "..v.netId, 0.25)
|
|||
|
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.resource = GetCurrentResourceName()
|
|||
|
|
|||
|
local callId = math.random(0, 10000000)
|
|||
|
local event = nil
|
|||
|
local entity = promise:new()
|
|||
|
|
|||
|
-- Callback
|
|||
|
event = RegisterNetEvent("Utility:Net:EntityCreated", function(_callId, uNetId)
|
|||
|
if _callId == callId then
|
|||
|
entity:resolve(uNetId)
|
|||
|
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)
|
|||
|
TriggerLatentServerEvent("Utility:Net:SetEntityCoords", 5120, uNetId, coords)
|
|||
|
|
|||
|
-- Instantly sync for local obj
|
|||
|
TriggerEvent("Utility:Net:RefreshCoords", uNetId, coords)
|
|||
|
end
|
|||
|
|
|||
|
UtilityNet.SetEntityRotation = function(uNetId, rot)
|
|||
|
TriggerLatentServerEvent("Utility:Net:SetEntityRotation", 5120, uNetId, rot)
|
|||
|
|
|||
|
-- Instantly sync for local obj
|
|||
|
TriggerEvent("Utility:Net:RefreshRotation", uNetId, rot)
|
|||
|
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
|
|||
|
--#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
|