295 lines
		
	
	
		
			No EOL
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			No EOL
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local NextId = 1
 | |
| 
 | |
| UtilityNet = UtilityNet or {}
 | |
| 
 | |
| -- options = {
 | |
| --     resource = string (used internally)
 | |
| --     replace = boolean (replace an already existing object, without creating a new one)
 | |
| --     searchDistance = number (default 5.0, replace search distance)
 | |
| --     door = boolean (if true will spawn the entity with door flag)
 | |
| -- }
 | |
| 
 | |
| UtilityNet.CreateEntity = function(model, coords, options, callId)
 | |
|     --#region Checks
 | |
|     if not model or (type(model) ~= "string" and type(model) ~= "number") then
 | |
|         error("Invalid model, got "..type(model).." expected string or number", 0)
 | |
|     else
 | |
|         if type(model) == "string" then
 | |
|             model = GetHashKey(model)
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     if not coords or type(coords) ~= "vector3" then
 | |
|         error("Invalid coords, got "..type(coords).." expected vector3", 0)
 | |
|     end
 | |
| 
 | |
|     options = options or {}
 | |
|     --#endregion
 | |
| 
 | |
|     --#region Event
 | |
|     TriggerEvent("Utility:Net:EntityCreating", model, coords, options)
 | |
| 
 | |
|     -- EntityCreating event can be canceled, in that case we dont create the entity
 | |
|     if WasEventCanceled() then 
 | |
|         return -1
 | |
|     end
 | |
|     --#endregion
 | |
| 
 | |
|     local entities = GlobalState.Entities
 | |
|     local slice = GetSliceFromCoords(coords)
 | |
|     
 | |
|     local object = {
 | |
|         id = NextId,
 | |
|         model = model,
 | |
|         coords = coords,
 | |
|         slice = slice,
 | |
|         options = options,
 | |
|         createdBy = options.resource or GetInvokingResource(),
 | |
|     }
 | |
| 
 | |
|     if not entities[slice] then
 | |
|         entities[slice] = {}
 | |
|     end
 | |
| 
 | |
|     entities[slice][object.id] = object
 | |
|     GlobalState.Entities = entities
 | |
| 
 | |
|     RegisterEntityState(object.id)
 | |
|     NextId = NextId + 1
 | |
| 
 | |
|     TriggerLatentClientEvent("Utility:Net:EntityCreated", -1, 5120, callId, object.id)
 | |
|     return object.id
 | |
| end
 | |
| 
 | |
| UtilityNet.DeleteEntity = function(uNetId)
 | |
|     --#region Checks
 | |
|     if type(uNetId) ~= "number" then
 | |
|         error("Invalid uNetId, got "..type(uNetId).." expected number", 2)
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     -- Invalid Id
 | |
|     if uNetId == -1 then
 | |
|         error("Invalid uNetId, got -1", 2)
 | |
|         return
 | |
|     end
 | |
|     --#endregion
 | |
| 
 | |
|     --#region Event
 | |
|     TriggerEvent("Utility:Net:EntityDeleting", uNetId)
 | |
| 
 | |
|     -- EntityDeleting event can be canceled, in that case we dont create the entity
 | |
|     if WasEventCanceled() then
 | |
|         return
 | |
|     end
 | |
|     --#endregion
 | |
| 
 | |
| 
 | |
|     local entities = GlobalState.Entities
 | |
|     local entity = UtilityNet.InternalFindFromNetId(uNetId)
 | |
| 
 | |
|     if entity then
 | |
|         entities[entity.slice][entity.id] = nil
 | |
|     end
 | |
| 
 | |
|     GlobalState.Entities = entities
 | |
| 
 | |
|     TriggerLatentEventForListeners("Utility:Net:RequestDeletion", uNetId, 5120, uNetId)
 | |
|     ClearEntityStates(uNetId) -- Clear states after trigger
 | |
| end
 | |
| 
 | |
| local queues = {
 | |
|     ModelsRenderDistance = {},
 | |
|     Entities = {},
 | |
| }
 | |
| 
 | |
| local function StartQueueUpdateLoop(bagkey)
 | |
|     local queue = queues[bagkey]
 | |
| 
 | |
|     Citizen.CreateThread(function()
 | |
|         while queue.updateLoop do
 | |
|             -- Nothing added in the last 100ms
 | |
|             if (GetGameTimer() - queue.lastInt) > 200 then
 | |
|                 local old = GlobalState[bagkey]
 | |
| 
 | |
|                 if bagkey == "Entities" then
 | |
|                     UtilityNet.ForEachEntity(function(entity)
 | |
|                         if queue[entity.id] then
 | |
|                             -- Rotation need to be handled separately
 | |
|                             if queue[entity.id].rotation then
 | |
|                                 old[entity.slice][entity.id].options.rotation = queue[entity.id].rotation
 | |
|                                 queue[entity.id].rotation = nil
 | |
|                             end
 | |
| 
 | |
|                             for k,v in pairs(queue[entity.id]) do
 | |
|                                 -- If slice need to be updated, move entity to new slice 
 | |
|                                 if k == "slice" and v ~= entity.slice then
 | |
|                                     local newSlice = v
 | |
| 
 | |
|                                     old[newSlice][entity.id] = old[entity.slice][entity.id] -- Copy to new slice
 | |
|                                     old[entity.slice][entity.id] = nil -- Remove from old
 | |
| 
 | |
|                                     entity = old[newSlice][entity.id] -- Update entity variable
 | |
|                                 end
 | |
| 
 | |
|                                 old[entity.slice][entity.id][k] = v
 | |
|                             end
 | |
|                         end
 | |
|                     end)
 | |
|                 else
 | |
|                     for k,v in pairs(old) do
 | |
|                         -- Net id need to be updated
 | |
|                         if queue[v.id] then
 | |
|                             -- Rotation need to be handled separately
 | |
|                             if queue[v.id].rotation then
 | |
|                                 v.options.rotation = queue[v.id].rotation
 | |
|                                 queue[v.id].rotation = nil
 | |
|                             end
 | |
|     
 | |
|                             for k2,v2 in pairs(queue[v.id]) do
 | |
|                                 v[k2] = v2
 | |
|                             end
 | |
|                         end
 | |
|                     end
 | |
|                 end
 | |
| 
 | |
| 
 | |
|                 -- Refresh GlobalState
 | |
|                 GlobalState[bagkey] = old
 | |
| 
 | |
|                 queues[bagkey].updateLoop = false
 | |
|                 queues[bagkey] = {}
 | |
|             end
 | |
|             Citizen.Wait(150)
 | |
|         end
 | |
|     end)
 | |
| end
 | |
| 
 | |
| local function InsertValueInQueue(bagkey, id, value)
 | |
|     -- If it is already in the queue with some values that need to be updated, we merge the 2 updates into 1
 | |
|     if queues[bagkey][id] then
 | |
|         queues[bagkey][id] = table.merge(queues[bagkey][id], value)
 | |
|     else
 | |
|         queues[bagkey][id] = value
 | |
|     end
 | |
| 
 | |
|     queues[bagkey].lastInt = GetGameTimer()
 | |
| 
 | |
|     if not queues[bagkey].updateLoop then
 | |
|         queues[bagkey].updateLoop = true
 | |
|         StartQueueUpdateLoop(bagkey)
 | |
|     end
 | |
| end
 | |
| 
 | |
| 
 | |
| UtilityNet.SetModelRenderDistance = function(model, distance)
 | |
|     if type(model) == "string" then
 | |
|         model = GetHashKey(model)
 | |
|     end
 | |
| 
 | |
|     local _ = GlobalState.ModelsRenderDistance
 | |
|     _[model] = distance
 | |
|     GlobalState.ModelsRenderDistance = _
 | |
| end
 | |
| 
 | |
| UtilityNet.SetEntityRotation = function(uNetId, newRotation)
 | |
|     local source = source
 | |
| 
 | |
|     if type(newRotation) ~= "vector3" then
 | |
|         error("Invalid rotation, got "..type(newRotation).." expected vector3", 2)
 | |
|     end
 | |
| 
 | |
|     InsertValueInQueue("Entities", uNetId, {rotation = newRotation})
 | |
| 
 | |
|     -- Except caller since it will be already updated
 | |
|     TriggerLatentEventForListenersExcept("Utility:Net:RefreshRotation", uNetId, 5120, source, uNetId, newRotation)
 | |
| end
 | |
| 
 | |
| UtilityNet.SetEntityCoords = function(uNetId, newCoords)
 | |
|     local source = source
 | |
| 
 | |
|     if type(newCoords) ~= "vector3" then
 | |
|         error("Invalid coords, got "..type(newCoords).." expected vector3", 2)
 | |
|     end
 | |
| 
 | |
|     InsertValueInQueue("Entities", uNetId, {coords = newCoords, slice = GetSliceFromCoords(newCoords)})
 | |
|     
 | |
|     -- Except caller since it will be already updated
 | |
|     TriggerLatentEventForListenersExcept("Utility:Net:RefreshCoords", uNetId, 5120, source, uNetId, newCoords)
 | |
| end
 | |
| 
 | |
| UtilityNet.SetEntityModel = function(uNetId, model)
 | |
|     local source = source
 | |
| 
 | |
|     if type(model) ~= "number" and type(model) ~= "string" then
 | |
|         error("Invalid model, got "..type(model).." expected string or number", 2)
 | |
|     end
 | |
| 
 | |
|     if type(model) == "string" then
 | |
|         model = GetHashKey(model)
 | |
|     end
 | |
| 
 | |
|     InsertValueInQueue("Entities", uNetId, {model = model})
 | |
| 
 | |
|     -- Except caller since it will be already updated
 | |
|     TriggerLatentEventForListenersExcept("Utility:Net:RefreshModel", uNetId, 5120, source, uNetId, model)
 | |
| end
 | |
| 
 | |
| --#region Events
 | |
| UtilityNet.RegisterEvents = function()
 | |
|     RegisterNetEvent("Utility:Net:CreateEntity", function(callId, model, coords, options)
 | |
|         UtilityNet.CreateEntity(model, coords, options, callId)
 | |
|     end)
 | |
|     
 | |
|     RegisterNetEvent("Utility:Net:DeleteEntity", function(uNetId)
 | |
|         UtilityNet.DeleteEntity(uNetId)
 | |
|     end)
 | |
|     
 | |
|     RegisterNetEvent("Utility:Net:SetModelRenderDistance", function(model, distance)
 | |
|         UtilityNet.SetModelRenderDistance(model, distance)
 | |
|     end)
 | |
| 
 | |
|     RegisterNetEvent("Utility:Net:AttachToEntity", function(uNetId, object, params)
 | |
|         local state = UtilityNet.State(uNetId)
 | |
|         state.__attached = {
 | |
|             object = object,
 | |
|             params = params
 | |
|         }
 | |
|     end)
 | |
| 
 | |
|     RegisterNetEvent("Utility:Net:DetachEntity", function(uNetId, newCoords)
 | |
|         local state = UtilityNet.State(uNetId)
 | |
| 
 | |
|         if state.__attached then
 | |
|             -- Update entity coords
 | |
|             if newCoords then
 | |
|                 UtilityNet.SetEntityCoords(uNetId, newCoords)
 | |
|             end
 | |
|             
 | |
|             state.__attached = nil
 | |
|         end
 | |
|     end)
 | |
| 
 | |
|     RegisterNetEvent("Utility:Net:SetEntityCoords", UtilityNet.SetEntityCoords)
 | |
|     RegisterNetEvent("Utility:Net:SetEntityModel", UtilityNet.SetEntityModel)
 | |
|     RegisterNetEvent("Utility:Net:SetEntityRotation", UtilityNet.SetEntityRotation)
 | |
| 
 | |
|     -- Clear all entities on resource stop
 | |
|     AddEventHandler("onResourceStop", function(resource)
 | |
|         if resource == GetCurrentResourceName() then
 | |
|             UtilityNet.ForEachEntity(function(v)
 | |
|                 TriggerLatentEventForListeners("Utility:Net:RequestDeletion", v, 5120, v)
 | |
|             end)
 | |
|         end
 | |
|     end)
 | |
| end
 | |
| --#endregion
 | |
| 
 | |
| -- Exports for server native.lua
 | |
| exports("CreateEntity", UtilityNet.CreateEntity)
 | |
| exports("DeleteEntity", UtilityNet.DeleteEntity)
 | |
| exports("SetModelRenderDistance", UtilityNet.SetModelRenderDistance)
 | |
| 
 | |
| exports("SetEntityModel", UtilityNet.SetEntityModel)
 | |
| exports("SetEntityCoords", UtilityNet.SetEntityCoords)
 | |
| exports("SetEntityRotation", UtilityNet.SetEntityRotation) | 
