324 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local Entities = {}
 | |
| local isServer = IsDuplicityVersion()
 | |
| local registeredFunctions = {}
 | |
| ReboundEntities = {}
 | |
| 
 | |
| function ReboundEntities.AddFunction(func)
 | |
|     assert(func, "Func is nil")
 | |
|     table.insert(registeredFunctions, func)
 | |
|     return #registeredFunctions
 | |
| end
 | |
| 
 | |
| function ReboundEntities.RemoveFunction(id)
 | |
|     assert(id and registeredFunctions[id], "Invalid ID")
 | |
|     registeredFunctions[id] = false
 | |
| end
 | |
| 
 | |
| function FireFunctions(...)
 | |
|     for _, func in pairs(registeredFunctions) do
 | |
|         func(...)
 | |
|     end
 | |
| end
 | |
| 
 | |
| function ReboundEntities.Register(entityData)
 | |
|     assert(entityData and entityData.position, "Invalid entity data")
 | |
|     entityData.id = entityData.id or Ids.CreateUniqueId(Entities)
 | |
|     entityData.isServer = isServer
 | |
|     setmetatable(entityData, { __tostring = function() return entityData.id end })
 | |
|     Entities[entityData.id] = entityData
 | |
|     FireFunctions(entityData)
 | |
|     return entityData
 | |
| end
 | |
| 
 | |
| local function Unregister(id)
 | |
|     Entities[id] = nil
 | |
| end
 | |
| 
 | |
| function ReboundEntities.GetSyncData(entityData, key)
 | |
|     return entityData[key]
 | |
| end
 | |
| 
 | |
| function ReboundEntities.GetAll()
 | |
|     return Entities
 | |
| end
 | |
| 
 | |
| function ReboundEntities.GetById(id)
 | |
|     return Entities[tostring(id)]
 | |
| end
 | |
| 
 | |
| function ReboundEntities.GetByModel(model)
 | |
|     local entities = {}
 | |
|     for _, entity in pairs(Entities) do
 | |
|         if entity.model == model then
 | |
|             table.insert(entities, entity)
 | |
|         end
 | |
|     end
 | |
|     return entities
 | |
| end
 | |
| 
 | |
| function ReboundEntities.GetClosest(pos)
 | |
|     local closest, closestDist = nil, 9999
 | |
|     for _, entity in pairs(Entities) do
 | |
|         local dist = #(pos - entity.position)
 | |
|         if dist < closestDist then
 | |
|             closest, closestDist = entity, dist
 | |
|         end
 | |
|     end
 | |
|     return closest
 | |
| end
 | |
| 
 | |
| function ReboundEntities.GetWithinRadius(pos, radius)
 | |
|     local entities = {}
 | |
|     for _, entity in pairs(Entities) do
 | |
|         if #(pos - entity.position) < radius then
 | |
|             table.insert(entities, entity)
 | |
|         end
 | |
|     end
 | |
|     return entities
 | |
| end
 | |
| 
 | |
| function ReboundEntities.SetOnSyncKeyChange(entityData, cb)
 | |
|     local data = ReboundEntities.GetById(entityData.id or entityData)
 | |
|     assert(data, "Entity not found")
 | |
|     data.onSyncKeyChange = cb
 | |
| end
 | |
| 
 | |
| exports('ReboundEntities', ReboundEntities)
 | |
| 
 | |
| if not isServer then goto client end
 | |
| 
 | |
| function ReboundEntities.Create(entityData, src)
 | |
|     local entity = ReboundEntities.Register(entityData)
 | |
|     assert(entity and entity.position, "Invalid entity data")
 | |
|     entity.rotation = entity.rotation or vector3(0, 0, entityData.heading or 0)
 | |
|     TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntity", src or -1, entity)
 | |
|     return entity
 | |
| end
 | |
| 
 | |
| 
 | |
| function ReboundEntities.SetCheckRestricted(entityData, cb)
 | |
|     assert(type(cb) == "function", "Check restricted is not a function")
 | |
|     entityData.restricted = cb
 | |
| end
 | |
| 
 | |
| 
 | |
| function ReboundEntities.CheckRestricted(src, entityData)
 | |
|     return entityData.restricted and entityData.restricted(tonumber(src), entityData) or false
 | |
| end
 | |
| 
 | |
| function ReboundEntities.Refresh(src)
 | |
|     TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", tonumber(src), ReboundEntities.GetAccessibleList(src))
 | |
| end
 | |
| 
 | |
| function ReboundEntities.Delete(id)
 | |
|     Unregister(id)
 | |
|     TriggerClientEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntity", -1, id)
 | |
| end
 | |
| 
 | |
| function ReboundEntities.DeleteMultiple(entityDatas)
 | |
|     local ids = {}
 | |
|     for _, data in pairs(entityDatas) do
 | |
|         Unregister(data.id)
 | |
|         table.insert(ids, data.id)
 | |
|     end
 | |
|     TriggerClientEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntities", -1, ids)
 | |
| end
 | |
| 
 | |
| function ReboundEntities.CreateMultiple(entityDatas, src, restricted)
 | |
|     local _entityDatas = {}
 | |
|     for _, data in pairs(entityDatas) do
 | |
|         local entity = ReboundEntities.Register(data)
 | |
|         assert(entity and entity.position, "Invalid entity data")
 | |
|         entity.rotation = entity.rotation or vector3(0, 0, 0)
 | |
|         if restricted then ReboundEntities.SetCheckRestricted(entity, restricted) end
 | |
|         if not ReboundEntities.CheckRestricted(src, entity) then
 | |
|             _entityDatas[entity.id] = entity
 | |
|         end
 | |
|     end
 | |
|     TriggerClientEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", src or -1, _entityDatas)
 | |
|     return _entityDatas
 | |
| end
 | |
| 
 | |
| function ReboundEntities.SetSyncData(entityData, key, value)
 | |
|     entityData[key] = value
 | |
|     if entityData.onSyncKeyChange then
 | |
|         entityData.onSyncKeyChange(entityData, key, value)
 | |
|     end
 | |
|     TriggerClientEvent(GetCurrentResourceName() .. ":client:SetReboundSyncData", -1, entityData.id, key, value)
 | |
| end
 | |
| 
 | |
| function ReboundEntities.GetAccessibleList(src)
 | |
|     local list = {}
 | |
|     for _, data in pairs(ReboundEntities.GetAll()) do
 | |
|         if not ReboundEntities.CheckRestricted(src, data) then
 | |
|             list[data.id] = data
 | |
|         end
 | |
|     end
 | |
|     return list
 | |
| end
 | |
| 
 | |
| RegisterNetEvent("playerJoining", function()
 | |
|     ReboundEntities.Refresh(source)
 | |
| end)
 | |
| 
 | |
| AddEventHandler('onResourceStart', function(resource)
 | |
|     if resource ~= GetCurrentResourceName() then return end
 | |
|     Wait(1000)
 | |
|     for _, src in pairs(GetPlayers()) do
 | |
|         ReboundEntities.Refresh(src)
 | |
|     end
 | |
| end)
 | |
| 
 | |
| ::client::
 | |
| if isServer then return ReboundEntities end
 | |
| 
 | |
| function ReboundEntities.LoadModel(model)
 | |
|     assert(model, "Model is nil")
 | |
|     model = type(model) == "number" and model or GetHashKey(model) -- Corrected to GetHashKey
 | |
|     RequestModel(model)
 | |
|     for i = 1, 100 do
 | |
|         if HasModelLoaded(model) then return model end
 | |
|         Wait(100)
 | |
|     end
 | |
|     error(string.format("Failed to load model %s", model))
 | |
| end
 | |
| 
 | |
| function ReboundEntities.Spawn(entityData)
 | |
|     local model = entityData.model
 | |
|     local position = entityData.position
 | |
|     assert(position, "Position is nil")
 | |
|     local rotation = entityData.rotation or vector3(0, 0, 0)
 | |
|     local entity = model and CreateObject(ReboundEntities.LoadModel(model), position.x, position.y, position.z, false, false, false)
 | |
|     if entity then SetEntityRotation(entity, rotation.x, rotation.y, rotation.z) end
 | |
|     if entityData.onSpawn then entity = entityData.onSpawn(entityData, entity) or entity end
 | |
|     return entity, true
 | |
| end
 | |
| 
 | |
| function ReboundEntities.SetOnSpawn(id, cb)
 | |
|     local entity = ReboundEntities.GetById(tostring(id))
 | |
|     assert(entity, "Entity not found")
 | |
|     entity.onSpawn = cb
 | |
|     return true
 | |
| end
 | |
| 
 | |
| function ReboundEntities.Despawn(entityData)
 | |
|     local entity = entityData.entity
 | |
|     if entityData.onDespawn then entityData.onDespawn(entityData, entity) end
 | |
|     if entity and DoesEntityExist(entity) then DeleteEntity(entity) end
 | |
|     return nil, nil
 | |
| end
 | |
| 
 | |
| function ReboundEntities.SetOnDespawn(id, cb)
 | |
|     local entity = ReboundEntities.GetById(tostring(id))
 | |
|     assert(entity, "Entity not found")
 | |
|     entity.onDespawn = cb
 | |
|     return true
 | |
| end
 | |
| 
 | |
| local spawnLoopRunning = false
 | |
| 
 | |
| function ReboundEntities.SpawnLoop(distanceToCheck, waitTime)
 | |
|     if spawnLoopRunning then return end
 | |
|     spawnLoopRunning = true
 | |
|     distanceToCheck = distanceToCheck or 50
 | |
|     waitTime = waitTime or 2500
 | |
|     CreateThread(function()
 | |
|         while spawnLoopRunning do
 | |
|             for _, entity in pairs(Entities) do
 | |
|                 local pos = GetEntityCoords(PlayerPedId())
 | |
|                 local position = vector3(entity.position.x, entity.position.y, entity.position.z)
 | |
|                 local dist = #(pos - position)
 | |
|                 if dist <= distanceToCheck and not entity.entity then
 | |
|                     entity.entity, entity.inRange = ReboundEntities.Spawn(entity)
 | |
|                 elseif dist > distanceToCheck and entity.entity then
 | |
|                     entity.entity, entity.inRange = ReboundEntities.Despawn(entity)
 | |
|                 end
 | |
|             end
 | |
|             Wait(waitTime)
 | |
|         end
 | |
|     end)
 | |
| end
 | |
| 
 | |
| function ReboundEntities.GetByEntity(entity)
 | |
|     for _, data in pairs(Entities) do
 | |
|         if data.entity == entity then return data end
 | |
|     end
 | |
| end
 | |
| 
 | |
| function ReboundEntities.CreateClient(entityData)
 | |
|     local entity = ReboundEntities.Register(entityData)
 | |
|     if entity then ReboundEntities.SpawnLoop() end
 | |
|     return entity
 | |
| end
 | |
| 
 | |
| function ReboundEntities.DeleteClient(id)
 | |
|     local entityData = ReboundEntities.GetById(tostring(id))
 | |
|     assert(not entityData.isServer, "Cannot delete server entity from client")
 | |
|     if entityData.entity then ReboundEntities.Despawn(entityData) end
 | |
|     Unregister(id)
 | |
|     return true
 | |
| end
 | |
| 
 | |
| function ReboundEntities.CreateMultipleClient(entityDatas)
 | |
|     local _entityDatas = {}
 | |
|     for _, data in pairs(entityDatas) do
 | |
|         local entityData = ReboundEntities.CreateClient(data)
 | |
|         _entityDatas[entityData.id] = entityData
 | |
|     end
 | |
|     return _entityDatas
 | |
| end
 | |
| 
 | |
| function ReboundEntities.DeleteMultipleClient(ids)
 | |
|     for _, id in pairs(ids) do
 | |
|         ReboundEntities.DeleteClient(id)
 | |
|     end
 | |
| end
 | |
| 
 | |
| local function DeleteFromServer(id)
 | |
|     local entityData = ReboundEntities.GetById(tostring(id))
 | |
|     if entityData then
 | |
|         entityData.isServer = nil
 | |
|         ReboundEntities.DeleteClient(id)
 | |
|     end
 | |
| end
 | |
| 
 | |
| local function DeleteMultipleFromServer(ids)
 | |
|     for _, id in pairs(ids) do
 | |
|         DeleteFromServer(id)
 | |
|     end
 | |
| end
 | |
| 
 | |
| RegisterNetEvent(GetCurrentResourceName() .. ":client:CreateReboundEntity", function(entityData)
 | |
|     ReboundEntities.CreateClient(entityData)
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntity", function(id)
 | |
|     DeleteFromServer(id)
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent(GetCurrentResourceName() .. ":client:CreateReboundEntities", function(entityDatas)
 | |
|     ReboundEntities.CreateMultipleClient(entityDatas)
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent(GetCurrentResourceName() .. ":client:DeleteReboundEntities", function(ids)
 | |
|     ReboundEntities.DeleteMultipleClient(ids)
 | |
| end)
 | |
| 
 | |
| RegisterNetEvent(GetCurrentResourceName() .. ":client:SetReboundSyncData", function(id, key, value)
 | |
|     local entityData = ReboundEntities.GetById(id)
 | |
|     if not entityData then return end
 | |
|     entityData[key] = value
 | |
|     if entityData.onSyncKeyChange then
 | |
|         entityData.onSyncKeyChange(entityData, key, value)
 | |
|     end
 | |
| end)
 | |
| 
 | |
| AddEventHandler('onResourceStop', function(resource)
 | |
|     if resource ~= GetCurrentResourceName() then return end
 | |
|     spawnLoopRunning = false
 | |
|     for _, entity in pairs(Entities) do
 | |
|         if entity.entity then DeleteEntity(tonumber(entity.entity)) end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| return ReboundEntities
 | 
