forked from Simnation/Main
red
This commit is contained in:
parent
9178871ecd
commit
640cdd069b
9 changed files with 583 additions and 269 deletions
|
@ -2,8 +2,14 @@ local Entity = Entity
|
|||
|
||||
local DebugRendering = false
|
||||
local DebugInfos = false
|
||||
|
||||
-- Used to prevent that the main loop tries to render an entity that has/his been/being deleted
|
||||
-- (the for each entity itearate over the old entities until next cycle and so will try to render a deleted entity)
|
||||
local DeletedEntities = {}
|
||||
|
||||
local EntitiesLoaded = false
|
||||
local Entities = {}
|
||||
|
||||
--#region Local functions
|
||||
local GetActiveSlices = function()
|
||||
local slices = GetSurroundingSlices(currentSlice)
|
||||
|
@ -76,41 +82,58 @@ local UnrenderLocalEntity = function(uNetId)
|
|||
local entity = UtilityNet.GetEntityFromUNetId(uNetId)
|
||||
|
||||
if DoesEntityExist(entity) then
|
||||
TriggerEvent("Utility:Net:OnUnrender", uNetId, entity, GetEntityModel(entity))
|
||||
local state = Entity(entity).state
|
||||
|
||||
Citizen.SetTimeout(1, function()
|
||||
if not DoesEntityExist(entity) then
|
||||
if DebugInfos then
|
||||
warn("UnrenderLocalEntity: entity with uNetId: "..uNetId.." already unrendered, skipping this call")
|
||||
if not state.preserved then
|
||||
TriggerEvent("Utility:Net:OnUnrender", uNetId, entity, GetEntityModel(entity))
|
||||
end
|
||||
|
||||
if not DoesEntityExist(entity) then
|
||||
if DebugInfos then
|
||||
warn("UnrenderLocalEntity: entity with uNetId: "..uNetId.." already unrendered, skipping this call")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Remove state change handler (currently used only for attaching)
|
||||
if state.changeHandler then
|
||||
UtilityNet.RemoveStateBagChangeHandler(state.changeHandler)
|
||||
state.changeHandler = nil
|
||||
end
|
||||
|
||||
if state.found then
|
||||
if state.door then
|
||||
if DoesEntityExist(state.door) then
|
||||
SetEntityVisible(state.door, true)
|
||||
SetEntityCollision(state.door, true, true)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local state = Entity(entity).state
|
||||
|
||||
-- Remove state change handler (currently used only for attaching)
|
||||
if state.changeHandler then
|
||||
UtilityNet.RemoveStateBagChangeHandler(state.changeHandler)
|
||||
state.changeHandler = nil
|
||||
end
|
||||
|
||||
if state.found then
|
||||
else
|
||||
local model = GetEntityModel(entity)
|
||||
|
||||
-- Show map object
|
||||
RemoveModelHide(GetEntityCoords(entity), 0.1, model)
|
||||
end
|
||||
|
||||
if state.preserved then
|
||||
SetEntityAsNoLongerNeeded(entity)
|
||||
else
|
||||
DeleteEntity(entity)
|
||||
end
|
||||
|
||||
state.rendered = false
|
||||
EntitiesStates[uNetId] = nil
|
||||
TriggerLatentServerEvent("Utility:Net:RemoveStateListener", 5120, uNetId)
|
||||
end)
|
||||
end
|
||||
|
||||
if not state.preserved then
|
||||
DeleteEntity(entity)
|
||||
end
|
||||
|
||||
state.rendered = false
|
||||
EntitiesStates[uNetId] = nil
|
||||
TriggerLatentServerEvent("Utility:Net:RemoveStateListener", 5120, uNetId)
|
||||
|
||||
if state.preserved then
|
||||
TriggerEvent("Utility:Net:OnUnrender", uNetId, entity, GetEntityModel(entity))
|
||||
|
||||
-- Max 5000ms for the entity to be deleted if it was preserved, if it still exists, delete it (this prevents entity leaks)
|
||||
Citizen.SetTimeout(5000, function()
|
||||
if DoesEntityExist(entity) then
|
||||
warn("UnrenderLocalEntity: entity with uNetId: "..uNetId.." was preserved for more than 5 seconds, deleting it now")
|
||||
DeleteEntity(entity)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
LocalEntities[uNetId] = nil
|
||||
|
@ -141,17 +164,44 @@ local RenderLocalEntity = function(uNetId, entityData)
|
|||
local model = entityData.model
|
||||
local options = entityData.options
|
||||
|
||||
if not options.replace then
|
||||
if not IsModelValid(model) then
|
||||
error("RenderLocalEntity: Model "..model.." is not valid, uNetId: "..uNetId)
|
||||
if options.abstract then
|
||||
if options.replace then
|
||||
error("RenderLocalEntity: abstract entities can't have the \"replace\" option, uNetId: "..uNetId.." model: "..model)
|
||||
end
|
||||
|
||||
if not IsModelValid(model) then
|
||||
RegisterArchetypes(function()
|
||||
return {
|
||||
{
|
||||
flags = 139296,
|
||||
bbMin = vector3(-0.1, -0.1, -0.1),
|
||||
bbMax = vector3(0.1, 0.1, 0.1),
|
||||
bsCentre = vector3(0.0, 0.0, 0.0),
|
||||
bsRadius = 1.0,
|
||||
name = model,
|
||||
textureDictionary = '',
|
||||
physicsDictionary = '',
|
||||
assetName = model,
|
||||
assetType = 'ASSET_TYPE_DRAWABLE',
|
||||
lodDist = 999,
|
||||
specialAttribute = 0
|
||||
}
|
||||
}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
if not IsModelValid(model) then
|
||||
error("RenderLocalEntity: Model "..tostring(model).." is not valid, uNetId: "..uNetId)
|
||||
end
|
||||
|
||||
if not options.abstract then
|
||||
local start = GetGameTimer()
|
||||
while not HasModelLoaded(model) do
|
||||
if (GetGameTimer() - start) > 5000 then
|
||||
error("RenderLocalEntity: Model "..model.." failed to load, uNetId: "..uNetId)
|
||||
end
|
||||
|
||||
|
||||
RequestModel(model)
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
@ -188,15 +238,23 @@ local RenderLocalEntity = function(uNetId, entityData)
|
|||
local distance = options.door and 1.5 or 0.1
|
||||
|
||||
if options.door and interior ~= 0 then
|
||||
Entity(obj).state.door = _obj
|
||||
|
||||
-- Doors inside interiors need to be deleted
|
||||
-- If not deleted the game will be recreate them every time the interior is reloaded (player exit and then re-enter)
|
||||
-- And so there will be 2 copies of the same door
|
||||
DeleteEntity(_obj)
|
||||
SetEntityVisible(_obj, false)
|
||||
SetEntityCollision(_obj, false, false)
|
||||
else
|
||||
CreateModelHideExcludingScriptObjects(coords, distance, model)
|
||||
end
|
||||
else
|
||||
obj = CreateObject(model, coords, false, false, options.door)
|
||||
if options.abstract then
|
||||
obj = old_CreateObject(model, coords, false, false, options.door)
|
||||
else
|
||||
obj = CreateObject(model, coords, false, false, options.door)
|
||||
end
|
||||
|
||||
SetEntityCoords(obj, coords) -- This is required to ignore the pivot
|
||||
end
|
||||
|
||||
|
@ -210,6 +268,10 @@ local RenderLocalEntity = function(uNetId, entityData)
|
|||
SetEntityRotation(obj, options.rotation)
|
||||
end
|
||||
|
||||
if options.abstract then
|
||||
Entity(obj).state.abstract_model = model
|
||||
end
|
||||
|
||||
-- Always listen for __attached changes (attach/detach)
|
||||
state.changeHandler = UtilityNet.AddStateBagChangeHandler(uNetId, function(key, value)
|
||||
-- Exit if entity is no longer valid
|
||||
|
@ -226,10 +288,12 @@ local RenderLocalEntity = function(uNetId, entityData)
|
|||
--print("Detach")
|
||||
DetachEntity(obj, true, true)
|
||||
end
|
||||
|
||||
LocalEntities[uNetId].attached = value
|
||||
end
|
||||
end)
|
||||
|
||||
LocalEntities[uNetId] = {obj=obj, slice=entityData.slice}
|
||||
LocalEntities[uNetId] = {obj=obj, slice=entityData.slice, createdBy = entityData.createdBy, attached = state.__attached}
|
||||
|
||||
-- Fetch initial state
|
||||
ServerRequestEntityStates(uNetId)
|
||||
|
@ -260,28 +324,26 @@ local CanEntityBeRendered = function(uNetId, entityData, slices)
|
|||
end
|
||||
|
||||
-- Check if entity is within drawing slices (if provided)
|
||||
if slices and not slices[entityData.slice] then
|
||||
if slices and not table.find(slices, entityData.slice) then
|
||||
return false
|
||||
end
|
||||
|
||||
local state = UtilityNet.State(uNetId)
|
||||
|
||||
if DeletedEntities[uNetId] then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Render only if within render distance
|
||||
if not state.__attached then
|
||||
local coords = GetEntityCoords(PlayerPedId())
|
||||
local modelsRenderDistance = GlobalState.ModelsRenderDistance
|
||||
local renderDistance = modelsRenderDistance[entityData.model] or 50.0
|
||||
|
||||
if #(entityData.coords - coords) > renderDistance then
|
||||
return false
|
||||
end
|
||||
-- Skip distance check if entity is rendered and attached (keep them alive)
|
||||
if LocalEntities[uNetId] and LocalEntities[uNetId].attached then
|
||||
return true
|
||||
end
|
||||
|
||||
return true
|
||||
local coords = GetEntityCoords(PlayerPedId())
|
||||
local modelsRenderDistance = GlobalState.ModelsRenderDistance
|
||||
local hashmodel = type(entityData.model) == "number" and entityData.model or GetHashKey(entityData.model)
|
||||
local renderDistance = modelsRenderDistance[hashmodel] or 50.0
|
||||
|
||||
return #(entityData.coords - coords) < renderDistance
|
||||
end
|
||||
--#endregion
|
||||
|
||||
|
@ -315,7 +377,6 @@ StartUtilityNetRenderLoop = function()
|
|||
-- Render/Unrender near slices entities
|
||||
UtilityNet.ForEachEntity(function(v)
|
||||
nEntities = nEntities + 1
|
||||
|
||||
if not LocalEntities[v.id] and CanEntityBeRendered(v.id, v) then
|
||||
local obj = UtilityNet.GetEntityFromUNetId(v.id) or 0
|
||||
local state = Entity(obj).state or {}
|
||||
|
@ -342,10 +403,8 @@ StartUtilityNetRenderLoop = function()
|
|||
-- Unrender entities that are out of slice
|
||||
-- Run only if the slice has changed (so something can be out of the slice and need to be unrendered)
|
||||
if lastSlice ~= currentSlice then
|
||||
local entities = GlobalState.Entities
|
||||
|
||||
for netId, data in pairs(LocalEntities) do
|
||||
local entityData = entities[data.slice][netId]
|
||||
local entityData = Entities[data.slice][netId]
|
||||
|
||||
if not CanEntityBeRendered(netId, entityData) then
|
||||
UnrenderLocalEntity(netId)
|
||||
|
@ -368,8 +427,35 @@ StartUtilityNetRenderLoop = function()
|
|||
end
|
||||
|
||||
RegisterNetEvent("Utility:Net:RefreshModel", function(uNetId, model)
|
||||
local timeout = 3000
|
||||
local start = GetGameTimer()
|
||||
while not LocalEntities[uNetId] and (GetGameTimer() - start < 3000) do
|
||||
local entity, slice = nil
|
||||
|
||||
while not entity or not slice do
|
||||
entity, slice = UtilityNet.InternalFindFromNetId(uNetId)
|
||||
|
||||
if (GetGameTimer() - start) > timeout then
|
||||
error("UtilityNet:RefreshModel: Entity existance check timed out for uNetId "..tostring(uNetId))
|
||||
break
|
||||
end
|
||||
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
||||
if entity and Entities[slice] then
|
||||
Entities[slice][uNetId].model = model
|
||||
else
|
||||
error(
|
||||
"Utility:Net:RefreshModel: Entity not found for uNetId " .. tostring(uNetId) ..
|
||||
" setting model " .. tostring(model) ..
|
||||
" entity: " .. tostring(entity) ..
|
||||
", slice: " .. tostring(slice) ..
|
||||
", doesExist? " .. tostring(UtilityNet.DoesUNetIdExist(uNetId))
|
||||
)
|
||||
end
|
||||
|
||||
start = GetGameTimer()
|
||||
while not LocalEntities[uNetId] and (GetGameTimer() - start < timeout) do
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
||||
|
@ -390,13 +476,6 @@ RegisterNetEvent("Utility:Net:RefreshModel", function(uNetId, model)
|
|||
-- Tamper with the entity model and render again
|
||||
local entityData = UtilityNet.InternalFindFromNetId(uNetId)
|
||||
|
||||
if not entityData then
|
||||
error("RefreshModel: entity with uNetId: "..tostring(uNetId).." cant be found")
|
||||
return
|
||||
end
|
||||
|
||||
entityData.model = model
|
||||
|
||||
SetNetIdBeingBusy(uNetId, false)
|
||||
RenderLocalEntity(uNetId, entityData)
|
||||
|
||||
|
@ -415,68 +494,108 @@ RegisterNetEvent("Utility:Net:RefreshModel", function(uNetId, model)
|
|||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("Utility:Net:RefreshCoords", function(uNetId, coords)
|
||||
RegisterNetEvent("Utility:Net:RefreshCoords", function(uNetId, coords, skipPositionUpdate)
|
||||
local start = GetGameTimer()
|
||||
while not LocalEntities[uNetId] and (GetGameTimer() - start < 3000) do
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
||||
if LocalEntities[uNetId] then
|
||||
while not UtilityNet.IsReady(uNetId) or IsNetIdBusy(uNetId) do
|
||||
Citizen.Wait(100)
|
||||
end
|
||||
local entity, slice = UtilityNet.InternalFindFromNetId(uNetId)
|
||||
|
||||
SetNetIdBeingBusy(uNetId, true)
|
||||
SetEntityCoords(LocalEntities[uNetId].obj, coords)
|
||||
SetNetIdBeingBusy(uNetId, false)
|
||||
end
|
||||
end)
|
||||
if entity and Entities[slice] then
|
||||
local newSlice = GetSliceFromCoords(coords)
|
||||
|
||||
RegisterNetEvent("Utility:Net:RefreshRotation", function(uNetId, rotation)
|
||||
local start = GetGameTimer()
|
||||
while not LocalEntities[uNetId] and (GetGameTimer() - start < 3000) do
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
||||
if LocalEntities[uNetId] then
|
||||
while not UtilityNet.IsReady(uNetId) or IsNetIdBusy(uNetId) do
|
||||
Citizen.Wait(100)
|
||||
end
|
||||
|
||||
SetNetIdBeingBusy(uNetId, true)
|
||||
SetEntityRotation(LocalEntities[uNetId].obj, rotation)
|
||||
SetNetIdBeingBusy(uNetId, false)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("Utility:Net:EntityCreated", function(_callId, uNetId)
|
||||
local attempts = 0
|
||||
|
||||
while not UtilityNet.DoesUNetIdExist(uNetId) do
|
||||
attempts = attempts + 1
|
||||
|
||||
if attempts > 5 then
|
||||
if DebugRendering then
|
||||
error("EntityCreated", uNetId, "id not found after 10 attempts")
|
||||
if newSlice ~= slice then
|
||||
local entity = Entities[slice][uNetId]
|
||||
Entities[slice][uNetId] = nil
|
||||
|
||||
if not Entities[newSlice] then
|
||||
Entities[newSlice] = {}
|
||||
end
|
||||
return
|
||||
|
||||
Entities[newSlice][uNetId] = entity
|
||||
|
||||
slice = newSlice
|
||||
end
|
||||
Citizen.Wait(100)
|
||||
|
||||
Entities[slice][uNetId].coords = coords
|
||||
Entities[slice][uNetId].slice = newSlice
|
||||
end
|
||||
|
||||
if CanEntityBeRendered(uNetId) then
|
||||
if not skipPositionUpdate then
|
||||
while not LocalEntities[uNetId] and (GetGameTimer() - start < 3000) do
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
||||
if LocalEntities[uNetId] then
|
||||
while not UtilityNet.IsReady(uNetId) or IsNetIdBusy(uNetId) do
|
||||
Citizen.Wait(100)
|
||||
end
|
||||
|
||||
SetNetIdBeingBusy(uNetId, true)
|
||||
SetEntityCoords(LocalEntities[uNetId].obj, coords)
|
||||
SetNetIdBeingBusy(uNetId, false)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("Utility:Net:RefreshRotation", function(uNetId, rotation, skipRotationUpdate)
|
||||
local start = GetGameTimer()
|
||||
local entity, slice = UtilityNet.InternalFindFromNetId(uNetId)
|
||||
|
||||
if entity and Entities[slice] then
|
||||
Entities[slice][uNetId].options.rotation = rotation
|
||||
end
|
||||
|
||||
if not skipRotationUpdate then
|
||||
while not LocalEntities[uNetId] and (GetGameTimer() - start < 3000) do
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
||||
if LocalEntities[uNetId] then
|
||||
while not UtilityNet.IsReady(uNetId) or IsNetIdBusy(uNetId) do
|
||||
Citizen.Wait(100)
|
||||
end
|
||||
|
||||
SetNetIdBeingBusy(uNetId, true)
|
||||
SetEntityRotation(LocalEntities[uNetId].obj, rotation)
|
||||
SetNetIdBeingBusy(uNetId, false)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("Utility:Net:EntityCreated", function(_callId, object)
|
||||
local uNetId = object.id
|
||||
local slices = GetActiveSlices()
|
||||
|
||||
if not Entities[object.slice] then
|
||||
Entities[object.slice] = {}
|
||||
end
|
||||
|
||||
Entities[object.slice][object.id] = object
|
||||
|
||||
if CanEntityBeRendered(uNetId, object, slices) then
|
||||
if DebugRendering then
|
||||
print("RenderLocalEntity", uNetId, "EntityCreated")
|
||||
end
|
||||
|
||||
RenderLocalEntity(uNetId)
|
||||
RenderLocalEntity(uNetId, object)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("Utility:Net:RequestDeletion", function(uNetId)
|
||||
local entityData = UtilityNet.InternalFindFromNetId(uNetId)
|
||||
if not entityData then return end
|
||||
|
||||
local slice = GetSliceFromCoords(entityData.coords)
|
||||
|
||||
if LocalEntities[uNetId] then
|
||||
DeletedEntities[uNetId] = true
|
||||
UnrenderLocalEntity(uNetId)
|
||||
|
||||
if Entities[slice] then
|
||||
Entities[slice][uNetId] = nil
|
||||
end
|
||||
else
|
||||
if Entities[slice] then
|
||||
Entities[slice][uNetId] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
@ -487,6 +606,39 @@ Citizen.CreateThread(function()
|
|||
end
|
||||
end)
|
||||
|
||||
Citizen.CreateThread(function()
|
||||
RegisterNetEvent("Utility:Net:GetEntities", function(entities)
|
||||
Entities = entities
|
||||
EntitiesLoaded = true
|
||||
end)
|
||||
|
||||
TriggerServerEvent("Utility:Net:GetEntities")
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStop", function(resource)
|
||||
if resource == GetCurrentResourceName() then
|
||||
for k,v in pairs(LocalEntities) do
|
||||
Citizen.CreateThreadNow(function()
|
||||
DeletedEntities[k] = true
|
||||
UnrenderLocalEntity(k)
|
||||
end)
|
||||
end
|
||||
else
|
||||
for k,v in pairs(LocalEntities) do
|
||||
if v.createdBy == resource then
|
||||
if DebugRendering then
|
||||
print("Unrendering deleted entity", k)
|
||||
end
|
||||
|
||||
Citizen.CreateThreadNow(function()
|
||||
DeletedEntities[k] = true
|
||||
UnrenderLocalEntity(k)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Exports
|
||||
UtilityNet.GetEntityFromUNetId = function(uNetId)
|
||||
return LocalEntities[uNetId]?.obj
|
||||
|
@ -500,6 +652,37 @@ UtilityNet.GetUNetIdFromEntity = function(entity)
|
|||
end
|
||||
end
|
||||
|
||||
UtilityNet.GetuNetIdCreator = function(uNetId)
|
||||
return LocalEntities[uNetId]?.createdBy
|
||||
end
|
||||
|
||||
UtilityNet.GetEntityCreator = function(entity)
|
||||
return UtilityNet.GetuNetIdCreator(UtilityNet.GetUNetIdFromEntity(entity))
|
||||
end
|
||||
|
||||
UtilityNet.InternalFindFromNetId = function(uNetId)
|
||||
for sliceI, slice in pairs(Entities) do
|
||||
if slice[uNetId] then
|
||||
return slice[uNetId], sliceI
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
exports("GetEntityFromUNetId", UtilityNet.GetEntityFromUNetId)
|
||||
exports("GetUNetIdFromEntity", UtilityNet.GetUNetIdFromEntity)
|
||||
exports("GetuNetIdCreator", UtilityNet.GetuNetIdCreator)
|
||||
exports("GetEntityCreator", UtilityNet.GetEntityCreator)
|
||||
|
||||
exports("GetRenderedEntities", function() return LocalEntities end)
|
||||
exports("GetEntities", function(slice)
|
||||
while not EntitiesLoaded do
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
||||
if slice then
|
||||
return Entities[slice] or {}
|
||||
else
|
||||
return Entities
|
||||
end
|
||||
end)
|
||||
exports("InternalFindFromNetId", UtilityNet.InternalFindFromNetId)
|
|
@ -1,6 +1,25 @@
|
|||
EntitiesStates = {}
|
||||
|
||||
local function IsEntityStateLoaded(uNetId)
|
||||
return EntitiesStates[uNetId] ~= -1
|
||||
end
|
||||
|
||||
local function EnsureStateLoaded(uNetId)
|
||||
if not IsEntityStateLoaded(uNetId) then
|
||||
local start = GetGameTimer()
|
||||
while not IsEntityStateLoaded(uNetId) do
|
||||
if GetGameTimer() - start > 5000 then
|
||||
error("WaitUntilStateLoaded: entity "..tostring(uNetId).." state loading timed out")
|
||||
break
|
||||
end
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RegisterNetEvent("Utility:Net:UpdateStateValue", function(uNetId, key, value)
|
||||
EnsureStateLoaded(uNetId)
|
||||
|
||||
if not EntitiesStates[uNetId] then
|
||||
EntitiesStates[uNetId] = {}
|
||||
end
|
||||
|
@ -9,14 +28,49 @@ RegisterNetEvent("Utility:Net:UpdateStateValue", function(uNetId, key, value)
|
|||
end)
|
||||
|
||||
GetEntityStateValue = function(uNetId, key)
|
||||
if not EntitiesStates[uNetId] then
|
||||
return
|
||||
if not UtilityNet.GetEntityFromUNetId(uNetId) then -- If trying to get state of entity that isnt loaded
|
||||
local start = GetGameTimer()
|
||||
|
||||
local entity = nil
|
||||
while not entity do
|
||||
entity = UtilityNet.InternalFindFromNetId(uNetId)
|
||||
if GetGameTimer() - start > 2000 then
|
||||
error("GetEntityStateValue: entity "..tostring(uNetId).." doesnt exist, attempted to retrieve key: "..tostring(key))
|
||||
break
|
||||
end
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
||||
return ServerRequestEntityKey(uNetId, key)
|
||||
else
|
||||
EnsureStateLoaded(uNetId)
|
||||
|
||||
if not EntitiesStates[uNetId] then
|
||||
warn("GetEntityStateValue: entity "..tostring(uNetId).." has no loaded states, attempted to retrieve key: "..tostring(key))
|
||||
return
|
||||
end
|
||||
|
||||
return EntitiesStates[uNetId][key]
|
||||
end
|
||||
|
||||
return EntitiesStates[uNetId][key]
|
||||
end
|
||||
|
||||
ServerRequestEntityKey = function(uNetId, key)
|
||||
local p = promise:new()
|
||||
local event = nil
|
||||
|
||||
event = RegisterNetEvent("Utility:Net:GetStateValue"..uNetId, function(value)
|
||||
RemoveEventHandler(event)
|
||||
p:resolve(value)
|
||||
end)
|
||||
|
||||
TriggerServerEvent("Utility:Net:GetStateValue", uNetId, key)
|
||||
return Citizen.Await(p)
|
||||
end
|
||||
|
||||
ServerRequestEntityStates = function(uNetId)
|
||||
EntitiesStates[uNetId] = -1 -- Set as loading
|
||||
|
||||
local p = promise:new()
|
||||
local event = nil
|
||||
|
||||
|
@ -28,7 +82,7 @@ ServerRequestEntityStates = function(uNetId)
|
|||
TriggerServerEvent("Utility:Net:GetState", uNetId)
|
||||
local states = Citizen.Await(p)
|
||||
|
||||
EntitiesStates[uNetId] = states
|
||||
EntitiesStates[uNetId] = states or {}
|
||||
end
|
||||
|
||||
exports("GetEntityStateValue", GetEntityStateValue)
|
||||
|
|
|
@ -1447,6 +1447,28 @@ end
|
|||
|
||||
return values
|
||||
end
|
||||
|
||||
-- Uses table.clone for fast shallow copying (memcpy) before checking and doing actual deepcopy for nested tables
|
||||
-- Handles circular references via seen table
|
||||
-- Significantly faster (~50%) than doing actual deepcopy for flat or lightly-nested structures
|
||||
---@param orig table
|
||||
---@return table
|
||||
table.deepcopy = function(orig, seen)
|
||||
if type(orig) ~= "table" then return orig end
|
||||
seen = seen or {}
|
||||
if seen[orig] then return seen[orig] end
|
||||
|
||||
local copy = table.clone(orig)
|
||||
seen[orig] = copy
|
||||
|
||||
for k, v in next, orig do
|
||||
if type(v) == "table" then
|
||||
copy[k] = table.deepcopy(v, seen)
|
||||
end
|
||||
end
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
math.round = function(number, decimals)
|
||||
local _ = 10 ^ decimals
|
||||
|
@ -2512,7 +2534,8 @@ end
|
|||
NetworkRequestControlOfEntity(trolly)
|
||||
end
|
||||
|
||||
local bagObj = CreateObject("hei_p_m_bag_var22_arm_s", vector3(0.0, 0.0, 0.0), true)
|
||||
local playerCoords = GetEntityCoords(ped)
|
||||
local bagObj = CreateObject("hei_p_m_bag_var22_arm_s", playerCoords + vector3(0.0, 0.0, -6.0), true)
|
||||
SetEntityCollision(bagObj, false, true)
|
||||
|
||||
-- Intro
|
||||
|
@ -2984,13 +3007,26 @@ end
|
|||
--// UtilityNet //
|
||||
local CreatedEntities = {}
|
||||
|
||||
local old_GetEntityArchetypeName = GetEntityArchetypeName
|
||||
GetEntityArchetypeName = function(entity)
|
||||
if not entity or not DoesEntityExist(entity) then
|
||||
return ""
|
||||
end
|
||||
|
||||
local res = old_GetEntityArchetypeName(entity)
|
||||
|
||||
if res == "" then
|
||||
return Entity(entity)?.state?.abstract_model or res
|
||||
else
|
||||
return res
|
||||
end
|
||||
end
|
||||
|
||||
--#region API
|
||||
UtilityNet.ForEachEntity = function(fn, slices)
|
||||
if slices then
|
||||
local entities = GlobalState.Entities
|
||||
|
||||
for i = 1, #slices do
|
||||
local _entities = entities[slices[i]]
|
||||
local _entities = UtilityNet.GetEntities(slices[i])
|
||||
local n = 0
|
||||
|
||||
if _entities then
|
||||
|
@ -3009,7 +3045,7 @@ UtilityNet.ForEachEntity = function(fn, slices)
|
|||
end
|
||||
end
|
||||
else
|
||||
local entities = GlobalState.Entities
|
||||
local entities = UtilityNet.GetEntities()
|
||||
|
||||
if not entities then
|
||||
return
|
||||
|
@ -3035,14 +3071,6 @@ UtilityNet.ForEachEntity = function(fn, slices)
|
|||
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
|
||||
|
||||
|
@ -3071,7 +3099,9 @@ UtilityNet.SetDebug = function(state)
|
|||
for k,v in pairs(localEntities) do
|
||||
local state = UtilityNet.State(v.netId)
|
||||
|
||||
DrawText3Ds(GetEntityCoords(v.obj), "NetId: "..v.netId, 0.25)
|
||||
if DoesEntityExist(v.obj) then
|
||||
DrawText3Ds(GetEntityCoords(v.obj), "NetId: "..v.netId, 0.25)
|
||||
end
|
||||
end
|
||||
Citizen.Wait(1)
|
||||
end
|
||||
|
@ -3089,16 +3119,16 @@ UtilityNet.CreateEntity = function(model, coords, options)
|
|||
|
||||
-- Set resource name in options
|
||||
options = options or {}
|
||||
options.resource = GetCurrentResourceName()
|
||||
options.createdBy = GetCurrentResourceName()
|
||||
|
||||
local callId = math.random(0, 10000000)
|
||||
local event = nil
|
||||
local entity = promise:new()
|
||||
|
||||
-- Callback
|
||||
event = RegisterNetEvent("Utility:Net:EntityCreated", function(_callId, uNetId)
|
||||
event = RegisterNetEvent("Utility:Net:EntityCreated", function(_callId, object)
|
||||
if _callId == callId then
|
||||
entity:resolve(uNetId)
|
||||
entity:resolve(object.id)
|
||||
RemoveEventHandler(event)
|
||||
end
|
||||
end)
|
||||
|
@ -3207,18 +3237,18 @@ UtilityNet.DetachEntity = function(uNetId)
|
|||
end
|
||||
|
||||
-- Using a latent event to prevent blocking the network channel
|
||||
UtilityNet.SetEntityCoords = function(uNetId, coords)
|
||||
UtilityNet.SetEntityCoords = function(uNetId, coords, skipPositionUpdate)
|
||||
TriggerLatentServerEvent("Utility:Net:SetEntityCoords", 5120, uNetId, coords)
|
||||
|
||||
-- Instantly sync for local obj
|
||||
TriggerEvent("Utility:Net:RefreshCoords", uNetId, coords)
|
||||
TriggerEvent("Utility:Net:RefreshCoords", uNetId, coords, skipPositionUpdate)
|
||||
end
|
||||
|
||||
UtilityNet.SetEntityRotation = function(uNetId, rot)
|
||||
TriggerLatentServerEvent("Utility:Net:SetEntityRotation", 5120, uNetId, rot)
|
||||
UtilityNet.SetEntityRotation = function(uNetId, rot, skipRotationUpdate)
|
||||
TriggerLatentServerEvent("Utility:Net:SetEntityRotation", 5120, uNetId, rot, skipRotationUpdate)
|
||||
|
||||
-- Instantly sync for local obj
|
||||
TriggerEvent("Utility:Net:RefreshRotation", uNetId, rot)
|
||||
TriggerEvent("Utility:Net:RefreshRotation", uNetId, rot, skipRotationUpdate)
|
||||
end
|
||||
|
||||
UtilityNet.SetEntityModel = function(uNetId, model)
|
||||
|
@ -3322,6 +3352,22 @@ end
|
|||
UtilityNet.GetUNetIdFromEntity = function(entity)
|
||||
return exports["utility_lib"]:GetUNetIdFromEntity(entity)
|
||||
end
|
||||
|
||||
UtilityNet.GetuNetIdCreator = function(uNetId)
|
||||
return exports["utility_lib"]:GetuNetIdCreator(uNetId)
|
||||
end
|
||||
|
||||
UtilityNet.GetEntityCreator = function(entity)
|
||||
return exports["utility_lib"]:GetEntityCreator(entity)
|
||||
end
|
||||
|
||||
UtilityNet.InternalFindFromNetId = function(uNetId)
|
||||
return exports["utility_lib"]:InternalFindFromNetId(uNetId)
|
||||
end
|
||||
|
||||
UtilityNet.GetEntities = function(slice)
|
||||
return exports["utility_lib"]:GetEntities(slice)
|
||||
end
|
||||
--#endregion
|
||||
|
||||
--#endregion
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
local NextId = 1
|
||||
local Entities = {}
|
||||
|
||||
UtilityNet = UtilityNet or {}
|
||||
|
||||
|
@ -10,24 +11,27 @@ UtilityNet = UtilityNet or {}
|
|||
-- }
|
||||
|
||||
UtilityNet.CreateEntity = function(model, coords, options, callId)
|
||||
options = options or {}
|
||||
local hashmodel = nil
|
||||
|
||||
--#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)
|
||||
hashmodel = GetHashKey(model)
|
||||
else
|
||||
hashmodel = 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)
|
||||
TriggerEvent("Utility:Net:EntityCreating", hashmodel, coords, options)
|
||||
|
||||
-- EntityCreating event can be canceled, in that case we dont create the entity
|
||||
if WasEventCanceled() then
|
||||
|
@ -35,29 +39,27 @@ UtilityNet.CreateEntity = function(model, coords, options, callId)
|
|||
end
|
||||
--#endregion
|
||||
|
||||
local entities = GlobalState.Entities
|
||||
local slice = GetSliceFromCoords(coords)
|
||||
|
||||
local object = {
|
||||
id = NextId,
|
||||
model = model,
|
||||
model = options.abstract and model or hashmodel,
|
||||
coords = coords,
|
||||
slice = slice,
|
||||
options = options,
|
||||
createdBy = options.resource or GetInvokingResource(),
|
||||
createdBy = options.createdBy or GetInvokingResource(),
|
||||
}
|
||||
|
||||
if not entities[slice] then
|
||||
entities[slice] = {}
|
||||
if not Entities[slice] then
|
||||
Entities[slice] = {}
|
||||
end
|
||||
|
||||
entities[slice][object.id] = object
|
||||
GlobalState.Entities = entities
|
||||
Entities[slice][object.id] = object
|
||||
|
||||
RegisterEntityState(object.id)
|
||||
NextId = NextId + 1
|
||||
|
||||
TriggerLatentClientEvent("Utility:Net:EntityCreated", -1, 5120, callId, object.id)
|
||||
TriggerLatentClientEvent("Utility:Net:EntityCreated", -1, 5120, callId, object)
|
||||
return object.id
|
||||
end
|
||||
|
||||
|
@ -85,103 +87,32 @@ UtilityNet.DeleteEntity = function(uNetId)
|
|||
--#endregion
|
||||
|
||||
|
||||
local entities = GlobalState.Entities
|
||||
local entity = UtilityNet.InternalFindFromNetId(uNetId)
|
||||
|
||||
if entity then
|
||||
entities[entity.slice][entity.id] = nil
|
||||
Entities[entity.slice][entity.id] = nil
|
||||
end
|
||||
|
||||
GlobalState.Entities = entities
|
||||
|
||||
TriggerLatentEventForListeners("Utility:Net:RequestDeletion", uNetId, 5120, uNetId)
|
||||
TriggerLatentClientEvent("Utility:Net:RequestDeletion", -1, 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)
|
||||
UtilityNet.InternalFindFromNetId = function(uNetId)
|
||||
for sliceI, slice in pairs(Entities) do
|
||||
if slice[uNetId] then
|
||||
return slice[uNetId], sliceI
|
||||
end
|
||||
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)
|
||||
UtilityNet.GetEntities = function(slice)
|
||||
if slice then
|
||||
return Entities[slice]
|
||||
else
|
||||
queues[bagkey][id] = value
|
||||
end
|
||||
|
||||
queues[bagkey].lastInt = GetGameTimer()
|
||||
|
||||
if not queues[bagkey].updateLoop then
|
||||
queues[bagkey].updateLoop = true
|
||||
StartQueueUpdateLoop(bagkey)
|
||||
return Entities
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
UtilityNet.SetModelRenderDistance = function(model, distance)
|
||||
if type(model) == "string" then
|
||||
model = GetHashKey(model)
|
||||
|
@ -192,30 +123,57 @@ UtilityNet.SetModelRenderDistance = function(model, distance)
|
|||
GlobalState.ModelsRenderDistance = _
|
||||
end
|
||||
|
||||
UtilityNet.SetEntityRotation = function(uNetId, newRotation)
|
||||
UtilityNet.SetEntityRotation = function(uNetId, newRotation, skipRotationUpdate)
|
||||
local source = source
|
||||
|
||||
if type(newRotation) ~= "vector3" then
|
||||
error("Invalid rotation, got "..type(newRotation).." expected vector3", 2)
|
||||
end
|
||||
|
||||
InsertValueInQueue("Entities", uNetId, {rotation = newRotation})
|
||||
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
|
||||
|
||||
-- Except caller since it will be already updated
|
||||
TriggerLatentEventForListenersExcept("Utility:Net:RefreshRotation", uNetId, 5120, source, uNetId, newRotation)
|
||||
TriggerLatentClientEvent("Utility:Net:RefreshRotation", -1, 5120, uNetId, newRotation, skipRotationUpdate)
|
||||
end
|
||||
|
||||
UtilityNet.SetEntityCoords = function(uNetId, newCoords)
|
||||
UtilityNet.SetEntityCoords = function(uNetId, newCoords, skipPositionUpdate)
|
||||
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)})
|
||||
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)
|
||||
|
||||
-- Except caller since it will be already updated
|
||||
TriggerLatentEventForListenersExcept("Utility:Net:RefreshCoords", uNetId, 5120, source, uNetId, newCoords)
|
||||
TriggerLatentClientEvent("Utility:Net:RefreshCoords", -1, 5120, uNetId, newCoords, skipPositionUpdate)
|
||||
end
|
||||
|
||||
UtilityNet.SetEntityModel = function(uNetId, model)
|
||||
|
@ -229,10 +187,12 @@ UtilityNet.SetEntityModel = function(uNetId, model)
|
|||
model = GetHashKey(model)
|
||||
end
|
||||
|
||||
InsertValueInQueue("Entities", uNetId, {model = model})
|
||||
local entity, slice = UtilityNet.InternalFindFromNetId(uNetId)
|
||||
|
||||
Entities[slice][uNetId].model = model
|
||||
|
||||
-- Except caller since it will be already updated
|
||||
TriggerLatentEventForListenersExcept("Utility:Net:RefreshModel", uNetId, 5120, source, uNetId, model)
|
||||
TriggerLatentClientEvent("Utility:Net:RefreshModel", -1, 5120, uNetId, model)
|
||||
end
|
||||
|
||||
--#region Events
|
||||
|
@ -244,7 +204,7 @@ UtilityNet.RegisterEvents = function()
|
|||
RegisterNetEvent("Utility:Net:DeleteEntity", function(uNetId)
|
||||
UtilityNet.DeleteEntity(uNetId)
|
||||
end)
|
||||
|
||||
|
||||
RegisterNetEvent("Utility:Net:SetModelRenderDistance", function(model, distance)
|
||||
UtilityNet.SetModelRenderDistance(model, distance)
|
||||
end)
|
||||
|
@ -274,13 +234,8 @@ UtilityNet.RegisterEvents = function()
|
|||
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
|
||||
RegisterNetEvent("Utility:Net:GetEntities", function()
|
||||
TriggerClientEvent("Utility:Net:GetEntities", source, UtilityNet.GetEntities())
|
||||
end)
|
||||
end
|
||||
--#endregion
|
||||
|
@ -289,6 +244,8 @@ end
|
|||
exports("CreateEntity", UtilityNet.CreateEntity)
|
||||
exports("DeleteEntity", UtilityNet.DeleteEntity)
|
||||
exports("SetModelRenderDistance", UtilityNet.SetModelRenderDistance)
|
||||
exports("GetEntities", UtilityNet.GetEntities)
|
||||
exports("InternalFindFromNetId", UtilityNet.InternalFindFromNetId)
|
||||
|
||||
exports("SetEntityModel", UtilityNet.SetEntityModel)
|
||||
exports("SetEntityCoords", UtilityNet.SetEntityCoords)
|
||||
|
|
|
@ -44,6 +44,7 @@ UpdateStateValueForListeners = function(uNetId, key, value)
|
|||
end
|
||||
|
||||
for k,v in pairs(EntitiesStates[uNetId].listeners) do
|
||||
TriggerEvent("Utility:Net:UpdateStateValue", uNetId, key, value)
|
||||
TriggerClientEvent("Utility:Net:UpdateStateValue", v, uNetId, key, value)
|
||||
end
|
||||
end
|
||||
|
@ -161,6 +162,7 @@ RegisterNetEvent("Utility:Net:GetState", function(uNetId)
|
|||
local source = source
|
||||
|
||||
if not EntitiesStates[uNetId] then
|
||||
warn("GetState: No state found for "..uNetId)
|
||||
TriggerClientEvent("Utility:Net:GetState"..uNetId, source, nil)
|
||||
return
|
||||
end
|
||||
|
@ -168,6 +170,19 @@ RegisterNetEvent("Utility:Net:GetState", function(uNetId)
|
|||
ListenStateUpdates(source, uNetId)
|
||||
TriggerClientEvent("Utility:Net:GetState"..uNetId, source, EntitiesStates[uNetId].states)
|
||||
end)
|
||||
|
||||
-- Single value
|
||||
RegisterNetEvent("Utility:Net:GetStateValue", function(uNetId, key)
|
||||
local source = source
|
||||
|
||||
if not EntitiesStates[uNetId] then
|
||||
warn("GetStateValue: No state found for "..uNetId)
|
||||
TriggerClientEvent("Utility:Net:GetStateValue"..uNetId, source, nil)
|
||||
return
|
||||
end
|
||||
|
||||
TriggerClientEvent("Utility:Net:GetStateValue"..uNetId, source, EntitiesStates[uNetId].states[key])
|
||||
end)
|
||||
--#endregion
|
||||
|
||||
-- On player disconnect remove all listeners of that player (prevent useless bandwidth usage)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
GlobalState.ModelsRenderDistance = {}
|
||||
GlobalState.Entities = {}
|
||||
|
||||
LoadUtilityFrameworkIfFound()
|
||||
|
||||
|
|
|
@ -30,23 +30,23 @@
|
|||
|
||||
--// Player //--
|
||||
-- Item
|
||||
AddItem = function(source, ...)
|
||||
AddItem = function(source, item, amount, metadata, slot)
|
||||
if ESX then
|
||||
xPlayer = ESX.GetPlayerFromId(source)
|
||||
xPlayer.addInventoryItem(...)
|
||||
xPlayer.addInventoryItem(item, amount, metadata, slot)
|
||||
else
|
||||
xPlayer = QBCore.Functions.GetPlayer(source)
|
||||
xPlayer.Functions.AddItem(...)
|
||||
xPlayer.Functions.AddItem(item, amount, slot, metadata)
|
||||
end
|
||||
end
|
||||
|
||||
RemoveItem = function(source, ...)
|
||||
RemoveItem = function(source, item, amount, metadata, slot)
|
||||
if ESX then
|
||||
xPlayer = ESX.GetPlayerFromId(source)
|
||||
xPlayer.removeInventoryItem(...)
|
||||
xPlayer.removeInventoryItem(item, amount, metadata, slot)
|
||||
else
|
||||
xPlayer = QBCore.Functions.GetPlayer(source)
|
||||
xPlayer.Functions.RemoveItem(...)
|
||||
xPlayer.Functions.RemoveItem(item, amount, slot, metadata)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -616,6 +616,28 @@
|
|||
return values
|
||||
end
|
||||
|
||||
-- Uses table.clone for fast shallow copying (memcpy) before checking and doing actual deepcopy for nested tables
|
||||
-- Handles circular references via seen table
|
||||
-- Significantly faster (~50%) than doing actual deepcopy for flat or lightly-nested structures
|
||||
---@param orig table
|
||||
---@return table
|
||||
table.deepcopy = function(orig, seen)
|
||||
if type(orig) ~= "table" then return orig end
|
||||
seen = seen or {}
|
||||
if seen[orig] then return seen[orig] end
|
||||
|
||||
local copy = table.clone(orig)
|
||||
seen[orig] = copy
|
||||
|
||||
for k, v in next, orig do
|
||||
if type(v) == "table" then
|
||||
copy[k] = table.deepcopy(v, seen)
|
||||
end
|
||||
end
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
math.round = function(number, decimals)
|
||||
local _ = 10 ^ decimals
|
||||
return math.floor((number * _) + 0.5) / (_)
|
||||
|
@ -744,32 +766,52 @@
|
|||
local CreatedEntities = {}
|
||||
UtilityNet = {}
|
||||
|
||||
UtilityNet.ForEachEntity = function(fn, slice)
|
||||
if slice then
|
||||
if not GlobalState.Entities[slice] then
|
||||
return
|
||||
end
|
||||
UtilityNet.ForEachEntity = function(fn, slices)
|
||||
if slices then
|
||||
local entities = UtilityNet.GetEntities(slices)
|
||||
|
||||
for k,v in pairs(GlobalState.Entities[slice]) do
|
||||
local ret = fn(v, k)
|
||||
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)
|
||||
|
||||
if ret ~= nil then
|
||||
return ret
|
||||
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
|
||||
if not GlobalState.Entities then
|
||||
local entities = UtilityNet.GetEntities()
|
||||
|
||||
if not entities then
|
||||
return
|
||||
end
|
||||
|
||||
for sliceI,slice in pairs(GlobalState.Entities) do
|
||||
for k2, v in pairs(slice) do
|
||||
-- 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
|
||||
|
@ -798,11 +840,7 @@ end
|
|||
|
||||
-- Returns the slice the entity is in
|
||||
UtilityNet.InternalFindFromNetId = function(uNetId)
|
||||
for sliceI, slice in pairs(GlobalState.Entities) do
|
||||
if slice[uNetId] then
|
||||
return slice[uNetId], sliceI
|
||||
end
|
||||
end
|
||||
return exports["utility_lib"]:InternalFindFromNetId(uNetId)
|
||||
end
|
||||
|
||||
UtilityNet.DoesUNetIdExist = function(uNetId)
|
||||
|
@ -835,16 +873,20 @@ UtilityNet.GetEntityModel = function(uNetId)
|
|||
end
|
||||
end
|
||||
|
||||
UtilityNet.GetEntities = function()
|
||||
return exports["utility_lib"]:GetEntities()
|
||||
end
|
||||
|
||||
UtilityNet.SetModelRenderDistance = function(model, distance)
|
||||
return exports["utility_lib"]:SetModelRenderDistance(model, distance)
|
||||
end
|
||||
|
||||
UtilityNet.SetEntityCoords = function(uNetId, newCoords)
|
||||
return exports["utility_lib"]:SetEntityCoords(uNetId, newCoords)
|
||||
UtilityNet.SetEntityCoords = function(uNetId, newCoords, skipPositionUpdate)
|
||||
return exports["utility_lib"]:SetEntityCoords(uNetId, newCoords, skipPositionUpdate)
|
||||
end
|
||||
|
||||
UtilityNet.SetEntityRotation = function(uNetId, newRotation)
|
||||
return exports["utility_lib"]:SetEntityRotation(uNetId, newRotation)
|
||||
UtilityNet.SetEntityRotation = function(uNetId, newRotation, skipRotationUpdate)
|
||||
return exports["utility_lib"]:SetEntityRotation(uNetId, newRotation, skipRotationUpdate)
|
||||
end
|
||||
|
||||
UtilityNet.DetachEntity = function(uNetId)
|
||||
|
@ -917,7 +959,25 @@ getValueAsStateTable = function(id, baseKey, depth)
|
|||
})
|
||||
end
|
||||
|
||||
UtilityNet.AddStateBagChangeHandler = function(uNetId, func)
|
||||
return AddEventHandler("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(id)
|
||||
if not id then
|
||||
error("UtilityNet.State: id is required, got nil", 2)
|
||||
end
|
||||
|
||||
local state = setmetatable({
|
||||
raw = function(self)
|
||||
return exports["utility_lib"]:GetEntityStateValue(id)
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.2.0
|
||||
1.3.7
|
|
@ -1,4 +1,4 @@
|
|||
local version = '1.2.0'
|
||||
local version = '1.3.7'
|
||||
local versionurl = "https://raw.githubusercontent.com/utility-library/utility_lib/master/version"
|
||||
|
||||
PerformHttpRequest(versionurl, function(error, _version, header)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue