Refactored entity behavior triggers to simplify argument passing and added OnSet handler for particle behaviors. Improved particle effect management and event handling, updated locale and lib initialization in several modules, and fixed skill metadata access. Also streamlined point zone logic and menu/zone system initialization for better reliability.
290 lines
11 KiB
Lua
290 lines
11 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
|
|
if entityData.model then
|
|
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
|
|
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)
|
|
local coords = vector3(entityData.coords.x, entityData.coords.y, entityData.coords.z)
|
|
entityData.oldCoords = entityData.oldCoords and vector3(entityData.oldCoords.x, entityData.oldCoords.y, entityData.oldCoords.z) or coords
|
|
local dist = #(coords - entityData.oldCoords)
|
|
if entityData.oldCoords and dist > 0.5 then
|
|
-- if entityData.spawned then
|
|
-- SetEntityCoords(entityData.spawned, coords.x, coords.y, coords.z, false, false, false, false)
|
|
-- 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
|
|
|
|
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
|
|
local oldData = entityData[key]
|
|
if oldData == value then return end
|
|
if entityData.OnSet then
|
|
pcall(function (...)
|
|
return entityData.OnSet(entityData, key, value, oldData)
|
|
end)
|
|
end
|
|
if value then
|
|
entityData[key] = value
|
|
Behaviors.Trigger("OnSet", entityData, key, value, oldData)
|
|
return
|
|
end
|
|
Behaviors.Trigger("OnSet", entityData, key, value, oldData)
|
|
entityData[key] = nil
|
|
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 |