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
 |