Files
community_bridge/lib/entities/client/client_entity.lua
T
Mr.Crowley cb9dbdf610 Adjust entity movement threshold and follow behavior
Increased the movement threshold for entity updates from 0.05 to 1.0 to reduce unnecessary position updates. Modified follow behavior to offset the entity's coordinates by -1 on the Z axis when updating, possibly to account for ground level or animation adjustments.
2025-09-03 01:23:01 -05:00

279 lines
10 KiB
Lua

Utility = Utility or Require("lib/utility/client/utility.lua")
Ids = Ids or Require("lib/utility/shared/ids.lua")
Point = Point or Require("lib/points/client/points.lua")
Behaviors = Behaviors or Require("lib/entities/shared/behaviors.lua")
local Entities = {} -- Stores entity data received from server
ClientEntity = {
Invoked = {}
} -- Renamed from BaseEntity
ClientEntity.Behaviors = Behaviors
--------------------
local function SpawnEntity(entityData)
if entityData.spawned and DoesEntityExist(entityData.spawned) then return end -- Already spawned
local loaded, model = Utility.LoadModel(entityData.model)
if not loaded then
print(string.format("[ClientEntity] Failed to load model %s for entity %s", entityData.model, entityData.id))
return
end
local entity = nil
local coords = entityData.coords
local rotation = entityData.rotation or vector3(0.0, 0.0, entityData.heading or 0.0) -- Default rotation if not provided
if entityData.entityType == 'object' then
entity = CreateObject(model, coords.x, coords.y, coords.z, false, false, false)
SetEntityRotation(entity, rotation.x, rotation.y, rotation.z, 2, true)
elseif entityData.entityType == 'ped' then
entity = CreatePed(4, model, coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or rotation.z, false, false)
elseif entityData.entityType == 'vehicle' then
entity = CreateVehicle(model, coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or rotation.z, false, false)
else
print(string.format("[ClientEntity] Unknown entity type '%s' for entity %s", entityData.entityType, entityData.id))
end
if entity and model then
entityData.spawned = entity
SetModelAsNoLongerNeeded(model)
SetEntityAsMissionEntity(entity, true, true)
FreezeEntityPosition(entity, true)
else
SetModelAsNoLongerNeeded(model)
end
if entityData.OnSpawn then
pcall(function (...)
return entityData.OnSpawn(entityData)
end)
end
Behaviors.Trigger("OnSpawn", entityData)
end
local function RemoveEntity(entityData)
entityData = entityData and entityData.args or entityData
if not entityData then return end
Behaviors.Trigger("OnRemove", entityData)
if entityData.OnRemove then
pcall(function (...)
return entityData.OnRemove(entityData)
end)
end
if entityData.spawned and DoesEntityExist(entityData.spawned) then
local entityHandle = entityData.spawned
entityData.spawned = nil
SetEntityAsMissionEntity(entityHandle, true, true)
DeleteEntity(entityHandle)
end
end
local function UpdateEntity(_entityData)
local entityData = ClientEntity.Get(_entityData.id)
if not entityData then return end
if entityData.OnUpdate then
pcall(function (...)
return entityData.OnUpdate(entityData)
end)
end
Behaviors.Trigger("OnUpdate", entityData)
if entityData.oldCoords and entityData.oldCoords ~= entityData.coords then
local coords = vector3(entityData.coords.x, entityData.coords.y, entityData.coords.z)
entityData.oldCoords = vector3(entityData.oldCoords.x, entityData.oldCoords.y, entityData.oldCoords.z)
local dist = #(coords.xyz - entityData.oldCoords.xyz)
if dist > 1.0 then
if entityData.spawned then
print("Moving entity:", entityData.id)
SetEntityCoords(entityData.spawned, coords.x, coords.y, coords.z, false, false, false, true)
end
-- ClientEntity.UpdateCoords(entityData.id, entityData.coords)
if entityData.OnMove then
pcall(function (...)
return entityData.OnMove(entityData)
end)
end
Behaviors.Trigger("OnMove", entityData, entityData.oldCoords, entityData.coords)
end
entityData.oldCoords = entityData.coords
end
if not entityData.oldRotation or entityData.oldRotation ~= entityData.rotation then
if entityData.spawned and DoesEntityExist(entityData.spawned) then
if entityData.entityType == 'object' then
SetEntityRotation(entityData.spawned, entityData.rotation.x, entityData.rotation.y, entityData.rotation.z, 2, true)
else -- Ped/Vehicle heading
SetEntityHeading(entityData.spawned, type(entityData.rotation) == 'number' and entityData.rotation or entityData.rotation.z)
end
end
entityData.oldRotation = entityData.rotation
end
if entityData.freeze ~= nil and entityData.spawned and IsEntityPositionFrozen(entityData.spawned) then
FreezeEntityPosition(entityData.spawned, entityData.freeze)
end
end
--- Registers an entity received from the server and sets up proximity spawning.
-- @param entityData table Data received from the server via 'community_bridge:client:CreateEntity'
function ClientEntity.Create(entityData)
entityData.id = entityData.id or Ids.CreateUniqueId(Entities)
if Entities[entityData.id] then return Entities[entityData.id] end
entityData.rotation = entityData.rotation or vector3(0.0, 0.0, entityData.heading or 0.0)
entityData.oldCoords = entityData.oldCoords or vector3(0.0, 0.0, 0.0)
entityData.oldRotation = entityData.rotation
entityData.invoked = entityData.invoked or GetInvokingResource() or "community_bridge"
local entityPoint = Point.Register(entityData.id, entityData.coords, entityData.spawnDistance or 50.0, entityData, SpawnEntity, RemoveEntity, UpdateEntity)
Entities[entityData.id] = entityPoint
ClientEntity.Invoked[entityData.invoked] = ClientEntity.Invoked[entityData.invoked] or {}
ClientEntity.Invoked[entityData.invoked][entityData.id] = entityData
Behaviors.Trigger("OnCreate", entityPoint)
return entityPoint
end
--Depricated use ClientEntity.Create instead
--- Registers an entity and spawns it in the world if not already spawned.
ClientEntity.Register = ClientEntity.Create
function ClientEntity.CreateBulk(entities)
local registeredEntities = {}
for _, entityData in pairs(entities) do
local entity = ClientEntity.Create(entityData)
registeredEntities[entity.id] = entity
end
return registeredEntities
end
-- Depricated use ClientEntity.CreateBulk instead
ClientEntity.RegisterBulk = ClientEntity.CreateBulk
--- Unregisters an entity and removes it from the world if spawned.
-- @param id string|number The ID of the entity to unregister.
function ClientEntity.Destroy(id)
local entityData = Entities[id]
if not entityData then return end
Behaviors.Trigger("OnDestroy", entityData)
Point.Remove(id)
RemoveEntity(entityData)
Entities[id] = nil
end
ClientEntity.Unregister = ClientEntity.Destroy
function ClientEntity.RegisterBehavior(id, behavior)
Behaviors.Create(id, behavior)
end
--- Sets a key-value pair for a client entity.
---@param id any
---@param key any
---@param value any
---@return nil
function ClientEntity.Set(id, key, value)
local entityData = ClientEntity.Get(id)
if not entityData then return print(string.format("[ClientEntity] SetKey: Entity %s does not exist", id)) end
entityData[key] = value
end
function ClientEntity.Get(id)
return Entities[id]
end
function ClientEntity.GetAll()
return Entities
end
function ClientEntity.OnCreate(_type, func)
ClientEntity.OnCreates = ClientEntity.OnCreates or {}
if not ClientEntity.OnCreates[_type] then
ClientEntity.OnCreates[_type] = {}
end
table.insert(ClientEntity.OnCreates[_type], func)
end
function ClientEntity.SetOnSpawn(id, func)
local entityData = Entities[id]
if not entityData then
print(string.format("[ClientEntity] SetOnSpawn: Entity %s does not exist", id))
return
end
entityData.OnSpawn = func
end
function ClientEntity.SetOnRemove(id, func)
local entityData = Entities[id]
if not entityData then
print(string.format("[ClientEntity] SetOnRemove: Entity %s does not exist", id))
return
end
entityData.OnRemove = func
end
function ClientEntity.UpdateCoords(id, coords)
local entityData = Entities[id]
if not entityData then
print(string.format("[ClientEntity] UpdateCoords: Entity %s does not exist", id))
return
end
entityData.coords = coords
Point.UpdateCoords(id, coords)
end
function ClientEntity.ChangeModel(id, model)
local entityData = Entities[id]
if not entityData then return print(string.format("[ClientEntity] ChangeModel: Entity %s does not exist", id)) end
entityData.model = model
if not entityData.spawned or not DoesEntityExist(entityData.spawned) then return end
RemoveEntity(entityData)
SpawnEntity(entityData)
end
-- Network Event Handlers
RegisterNetEvent("community_bridge:client:CreateEntity", function(entityData)
ClientEntity.Create(entityData)
end)
RegisterNetEvent("community_bridge:client:CreateEntities", function(entities)
ClientEntity.CreateBulk(entities)
end)
RegisterNetEvent("community_bridge:client:DeleteEntity", function(id)
ClientEntity.Unregister(id)
end)
RegisterNetEvent("community_bridge:client:UpdateEntity", function(id, data)
for k, v in pairs(data) do
ClientEntity.Set(id, k, v)
end
end)
local Loaded = false
AddEventHandler('community_bridge:Client:OnPlayerLoaded', function(resourceName)
if Loaded then return end
Loaded = true
end)
AddEventHandler('onResourceStart', function(resourceName)
if resourceName ~= GetCurrentResourceName() then return end
if Loaded then return end
Loaded = true
end)
-- Resource Stop Cleanup
AddEventHandler('onResourceStop', function(resourceName)
if resourceName == GetCurrentResourceName() then
for id, entityData in pairs(Entities) do
Point.Remove(id) -- Clean up point registration
RemoveEntity(entityData) -- Clean up spawned game entity
end
Entities = {} -- Clear local cache
else
for id, entityData in pairs(ClientEntity.Invoked[resourceName] or {}) do
Point.Remove(id) -- Clean up point registration
RemoveEntity(entityData) -- Clean up spawned game entity
Entities[id] = nil
end
end
end)
return ClientEntity