2025-06-07 08:51:21 +02:00
|
|
|
local NextId = 1
|
2025-08-12 16:56:50 +02:00
|
|
|
local Entities = {}
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
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)
|
2025-08-12 16:56:50 +02:00
|
|
|
options = options or {}
|
|
|
|
local hashmodel = nil
|
|
|
|
|
2025-06-07 08:51:21 +02:00
|
|
|
--#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
|
2025-08-12 16:56:50 +02:00
|
|
|
hashmodel = GetHashKey(model)
|
|
|
|
else
|
|
|
|
hashmodel = model
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not coords or type(coords) ~= "vector3" then
|
|
|
|
error("Invalid coords, got "..type(coords).." expected vector3", 0)
|
|
|
|
end
|
|
|
|
--#endregion
|
|
|
|
|
|
|
|
--#region Event
|
2025-08-12 16:56:50 +02:00
|
|
|
TriggerEvent("Utility:Net:EntityCreating", hashmodel, coords, options)
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
-- EntityCreating event can be canceled, in that case we dont create the entity
|
|
|
|
if WasEventCanceled() then
|
|
|
|
return -1
|
|
|
|
end
|
|
|
|
--#endregion
|
|
|
|
|
|
|
|
local slice = GetSliceFromCoords(coords)
|
|
|
|
|
|
|
|
local object = {
|
|
|
|
id = NextId,
|
2025-08-12 16:56:50 +02:00
|
|
|
model = options.abstract and model or hashmodel,
|
2025-06-07 08:51:21 +02:00
|
|
|
coords = coords,
|
|
|
|
slice = slice,
|
|
|
|
options = options,
|
2025-08-12 16:56:50 +02:00
|
|
|
createdBy = options.createdBy or GetInvokingResource(),
|
2025-06-07 08:51:21 +02:00
|
|
|
}
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
if not Entities[slice] then
|
|
|
|
Entities[slice] = {}
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
Entities[slice][object.id] = object
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
RegisterEntityState(object.id)
|
|
|
|
NextId = NextId + 1
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
TriggerLatentClientEvent("Utility:Net:EntityCreated", -1, 5120, callId, object)
|
2025-06-07 08:51:21 +02:00
|
|
|
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 entity = UtilityNet.InternalFindFromNetId(uNetId)
|
|
|
|
|
|
|
|
if entity then
|
2025-08-12 16:56:50 +02:00
|
|
|
Entities[entity.slice][entity.id] = nil
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
TriggerLatentClientEvent("Utility:Net:RequestDeletion", -1, 5120, uNetId)
|
2025-06-07 08:51:21 +02:00
|
|
|
ClearEntityStates(uNetId) -- Clear states after trigger
|
|
|
|
end
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
UtilityNet.InternalFindFromNetId = function(uNetId)
|
|
|
|
for sliceI, slice in pairs(Entities) do
|
|
|
|
if slice[uNetId] then
|
|
|
|
return slice[uNetId], sliceI
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
2025-08-12 16:56:50 +02:00
|
|
|
end
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
UtilityNet.GetEntities = function(slice)
|
|
|
|
if slice then
|
|
|
|
return Entities[slice]
|
2025-06-07 08:51:21 +02:00
|
|
|
else
|
2025-08-12 16:56:50 +02:00
|
|
|
return Entities
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
UtilityNet.SetModelRenderDistance = function(model, distance)
|
|
|
|
if type(model) == "string" then
|
|
|
|
model = GetHashKey(model)
|
|
|
|
end
|
|
|
|
|
|
|
|
local _ = GlobalState.ModelsRenderDistance
|
|
|
|
_[model] = distance
|
|
|
|
GlobalState.ModelsRenderDistance = _
|
|
|
|
end
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
UtilityNet.SetEntityRotation = function(uNetId, newRotation, skipRotationUpdate)
|
2025-06-07 08:51:21 +02:00
|
|
|
local source = source
|
|
|
|
|
|
|
|
if type(newRotation) ~= "vector3" then
|
|
|
|
error("Invalid rotation, got "..type(newRotation).." expected vector3", 2)
|
|
|
|
end
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
if newRotation.x ~= newRotation.x or newRotation.y ~= newRotation.y or newRotation.z ~= newRotation.z then
|
|
|
|
error("Invalid rotation, got "..type(newRotation).." (with NaN) expected vector3", 2)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local entity, slice = UtilityNet.InternalFindFromNetId(uNetId)
|
|
|
|
|
|
|
|
Entities[slice][uNetId].options.rotation = newRotation
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
-- Except caller since it will be already updated
|
2025-08-12 16:56:50 +02:00
|
|
|
TriggerLatentClientEvent("Utility:Net:RefreshRotation", -1, 5120, uNetId, newRotation, skipRotationUpdate)
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
UtilityNet.SetEntityCoords = function(uNetId, newCoords, skipPositionUpdate)
|
2025-06-07 08:51:21 +02:00
|
|
|
local source = source
|
|
|
|
|
|
|
|
if type(newCoords) ~= "vector3" then
|
|
|
|
error("Invalid coords, got "..type(newCoords).." expected vector3", 2)
|
|
|
|
end
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
if newCoords.x ~= newCoords.x or newCoords.y ~= newCoords.y or newCoords.z ~= newCoords.z then
|
|
|
|
error("Invalid coords, got "..type(newCoords).." (with NaN) expected vector3", 2)
|
|
|
|
end
|
|
|
|
|
|
|
|
local entity, slice = UtilityNet.InternalFindFromNetId(uNetId)
|
|
|
|
local newSlice = GetSliceFromCoords(newCoords)
|
|
|
|
|
|
|
|
if newSlice ~= slice then
|
|
|
|
local old = Entities[slice][uNetId]
|
|
|
|
|
|
|
|
if not Entities[newSlice] then
|
|
|
|
Entities[newSlice] = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
Entities[slice][uNetId] = nil
|
|
|
|
Entities[newSlice][uNetId] = old
|
|
|
|
|
|
|
|
slice = newSlice
|
|
|
|
end
|
|
|
|
Entities[slice][uNetId].coords = newCoords
|
|
|
|
Entities[slice][uNetId].slice = GetSliceFromCoords(newCoords)
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
-- Except caller since it will be already updated
|
2025-08-12 16:56:50 +02:00
|
|
|
TriggerLatentClientEvent("Utility:Net:RefreshCoords", -1, 5120, uNetId, newCoords, skipPositionUpdate)
|
2025-06-07 08:51:21 +02:00
|
|
|
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
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
local entity, slice = UtilityNet.InternalFindFromNetId(uNetId)
|
|
|
|
|
|
|
|
Entities[slice][uNetId].model = model
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
-- Except caller since it will be already updated
|
2025-08-12 16:56:50 +02:00
|
|
|
TriggerLatentClientEvent("Utility:Net:RefreshModel", -1, 5120, uNetId, model)
|
2025-06-07 08:51:21 +02:00
|
|
|
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)
|
2025-08-12 16:56:50 +02:00
|
|
|
|
2025-06-07 08:51:21 +02:00
|
|
|
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)
|
|
|
|
|
2025-08-12 16:56:50 +02:00
|
|
|
RegisterNetEvent("Utility:Net:GetEntities", function()
|
|
|
|
TriggerClientEvent("Utility:Net:GetEntities", source, UtilityNet.GetEntities())
|
2025-06-07 08:51:21 +02:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
--#endregion
|
|
|
|
|
|
|
|
-- Exports for server native.lua
|
|
|
|
exports("CreateEntity", UtilityNet.CreateEntity)
|
|
|
|
exports("DeleteEntity", UtilityNet.DeleteEntity)
|
|
|
|
exports("SetModelRenderDistance", UtilityNet.SetModelRenderDistance)
|
2025-08-12 16:56:50 +02:00
|
|
|
exports("GetEntities", UtilityNet.GetEntities)
|
|
|
|
exports("InternalFindFromNetId", UtilityNet.InternalFindFromNetId)
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
exports("SetEntityModel", UtilityNet.SetEntityModel)
|
|
|
|
exports("SetEntityCoords", UtilityNet.SetEntityCoords)
|
|
|
|
exports("SetEntityRotation", UtilityNet.SetEntityRotation)
|