1
0
Fork 0
forked from Simnation/Main
Main/resources/[standalone]/utility_lib/server/functions/utilityNet.lua
2025-06-07 08:51:21 +02:00

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)