1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-08-12 16:56:50 +02:00
parent 9178871ecd
commit 640cdd069b
9 changed files with 583 additions and 269 deletions

View file

@ -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)