From 68ca928033b7ee4c45fdde892403b48e093b8ae3 Mon Sep 17 00:00:00 2001 From: "Mr.Crowley" Date: Sun, 11 Jan 2026 18:36:07 -0600 Subject: [PATCH 1/3] Stipped away inefficient behavior system Refactored the entity system to be **property-driven** with automatic `On` change handlers. Added improved `Bridge.Entity.SetOnCreate` support so clients can identify and attach logic to **server-created entities** based on properties. Introduced standardized default properties on `Bridge.Entity.Create`, including: * `freeze` (defaults to frozen) * `invincible` * `targets` * `attach` * `anim` Added exported helper functions for all of the above to allow runtime updates after creation. --- lib/entities/client/client_entity.lua | 381 ++++++++++++-------- lib/entities/server/server_entity.lua | 9 +- lib/entities/shared/behaviors.lua | 121 ------- lib/entities/shared/behaviors/anim.lua | 62 ---- lib/entities/shared/behaviors/attach.lua | 52 --- lib/entities/shared/behaviors/clothing.lua | 82 ----- lib/entities/shared/behaviors/follow.lua | 75 ---- lib/entities/shared/behaviors/particles.lua | 94 ----- lib/entities/shared/behaviors/scenarios.lua | 64 ---- lib/entities/shared/behaviors/shop.lua | 58 --- lib/entities/shared/behaviors/stash.lua | 61 ---- lib/entities/shared/behaviors/targets.lua | 78 ---- lib/entities/shared/behaviors/weapon.lua | 54 --- 13 files changed, 239 insertions(+), 952 deletions(-) delete mode 100644 lib/entities/shared/behaviors.lua delete mode 100644 lib/entities/shared/behaviors/anim.lua delete mode 100644 lib/entities/shared/behaviors/attach.lua delete mode 100644 lib/entities/shared/behaviors/clothing.lua delete mode 100644 lib/entities/shared/behaviors/follow.lua delete mode 100644 lib/entities/shared/behaviors/particles.lua delete mode 100644 lib/entities/shared/behaviors/scenarios.lua delete mode 100644 lib/entities/shared/behaviors/shop.lua delete mode 100644 lib/entities/shared/behaviors/stash.lua delete mode 100644 lib/entities/shared/behaviors/targets.lua delete mode 100644 lib/entities/shared/behaviors/weapon.lua diff --git a/lib/entities/client/client_entity.lua b/lib/entities/client/client_entity.lua index 2f36f9b..f7601ee 100644 --- a/lib/entities/client/client_entity.lua +++ b/lib/entities/client/client_entity.lua @@ -1,18 +1,79 @@ 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 = {} + All = {}, + Invoked = {}, + OnCreates = {} } -- Renamed from BaseEntity -ClientEntity.Behaviors = Behaviors +---------------------------------------- --------------------- +---@alias ClientEntityId string|number +---@alias EntityHandle number + +---@class ClientEntityAnim +---@field dict string +---@field name string +---@field blendIn number|nil +---@field blendOut number|nil +---@field duration number|nil +---@field flags number|nil +---@field playbackRate number|nil +---@field onComplete function|nil + +---@class ClientEntityAttach +---@field target string|number Target reference. String may be a Bridge.Entity id; number may be a player server id; either may also be a FiveM entity handle. +---@field bone number|nil +---@field offset vector3|nil +---@field rotation vector3|nil + +---@class EntityData +---@field id ClientEntityId|nil If omitted, one is generated. +---@field coords vector3 +---@field entityType 'object'|'ped'|'vehicle' +---@field model string|number +---@field heading number|nil +---@field rotation vector3|nil +---@field spawnDistance number|nil +---@field freeze boolean|nil +---@field invincible boolean|nil +---@field targets table|nil Target options passed to Bridge.Target.AddLocalEntity. +---@field anim ClientEntityAnim|nil +---@field attach ClientEntityAttach|nil +---@field OnSpawn fun(entityData: EntityData)|nil +---@field OnRemove fun(entityData: EntityData)|nil +---@field spawned EntityHandle|nil FiveM entity handle created when the entry is in-range. + +-- Set the entity and apply any relevant properties such as freezing and targets. +local function SetEntityProperties(id) + local entityData = ClientEntity.Get(id) + if not entityData then return end + local entity = entityData.spawned + if not entity or not DoesEntityExist(entity) then return end + + if entityData.freeze == nil or entityData.freeze then + FreezeEntityPosition(entity, true) + end + if entityData.invincible then + SetEntityInvincible(entity, entityData.invincible) + end + if entityData.targets then + for k, v in pairs(entityData.targets) do + print(v.label) + end + ClientEntity.SetTargets(id, entityData.targets) + end + if entityData.anim then + ClientEntity.SetAnim(id, entityData.anim) + end + if entityData.attach then + ClientEntity.SetAttach(id, entityData.attach) + end +end local function SpawnEntity(entityData) if entityData.spawned and DoesEntityExist(entityData.spawned) then return end -- Already spawned - if entityData.model then + 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)) @@ -33,27 +94,19 @@ local function SpawnEntity(entityData) 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 + entityData.spawned = entity + SetEntityProperties(entityData.id) + SetModelAsNoLongerNeeded(model) end - if entityData.OnSpawn then - pcall(function (...) - return entityData.OnSpawn(entityData) - end) - end - Behaviors.Trigger("OnSpawn", entityData) + if not entityData.OnSpawn then return end + pcall(function (...) + return entityData.OnSpawn(entityData) + end) 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) @@ -67,70 +120,54 @@ local function RemoveEntity(entityData) 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 +--- Registers a ClientEntity definition and a proximity trigger that spawns/despawns it. +--- +--- Note: When in-range, the spawned FiveM entity handle is stored on `entityData.spawned`. +--- @param entityData EntityData +--- @return table Registered point/entity entry (the value stored in ClientEntity.All[entityData.id]). +function ClientEntity.Create(entityData) + entityData.id = entityData.id or Ids.CreateUniqueId(ClientEntity.All) + if ClientEntity.All[entityData.id] then return ClientEntity.All[entityData.id] end + entityData.rotation = entityData.rotation or vector3(0.0, 0.0, entityData.heading or 0.0) + entityData.invoked = entityData.invoked or GetInvokingResource() or "community_bridge" - 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) + for k, v in pairs(ClientEntity.OnCreates) do + if entityData[k] ~= nil then + local success, results = pcall(function() + return v(entityData) + end) + if success then + entityData = results or entityData 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.All[entityData.id] = entityPoint ClientEntity.Invoked[entityData.invoked] = ClientEntity.Invoked[entityData.invoked] or {} ClientEntity.Invoked[entityData.invoked][entityData.id] = entityData - Behaviors.Trigger("OnCreate", entityPoint) + + for k, v in pairs(entityPoint) do + ClientEntity.SetKey(entityData.id, k, v) + end return entityPoint end ---Depricated use ClientEntity.Create instead ---- Registers an entity and spawns it in the world if not already spawned. +--- Gets the entity that was spawned or nil if player is not in range +---@param id ClientEntityId +---@return EntityHandle|nil +function ClientEntity.GetEntity(id) + local entityData = ClientEntity.Get(id) + if not entityData then return nil end + return entityData.spawned +end +---Deprecated use ClientEntity.Create instead ClientEntity.Register = ClientEntity.Create - +--- Registers multiple entities. +--- @param entities table List of ClientEntity definitions. +--- @return table Map of ClientEntity id to registered entries. function ClientEntity.CreateBulk(entities) local registeredEntities = {} for _, entityData in pairs(entities) do @@ -139,96 +176,127 @@ function ClientEntity.CreateBulk(entities) 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. +--- Unregisters a ClientEntity registration and deletes its spawned FiveM entity (if any). +--- @param id ClientEntityId ClientEntity id (key used in ClientEntity.All / Point system), not a FiveM entity handle. function ClientEntity.Destroy(id) - local entityData = Entities[id] + local entityData = ClientEntity.All[id] if not entityData then return end - Behaviors.Trigger("OnDestroy", entityData) Point.Remove(id) - RemoveEntity(entityData) - Entities[id] = nil + RemoveEntity(entityData) + ClientEntity.All[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) +--- Sets a property on a registered ClientEntity entry. +--- If the entry defines an On handler (example: key='coords' -> OnCoords), it will be called. +--- @param id ClientEntityId ClientEntity id (key used in ClientEntity.All), not a FiveM entity handle. +--- @param key string Field name on the stored entity data table. +--- @param value any Value to set. +--- @return nil +function ClientEntity.SetKey(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) + local handlerKey = "On" .. key:sub(1, 1):upper() .. key:sub(2) + local handler = rawget(entityData, handlerKey) + if handler then + pcall(function() + handler(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 + entityData[key] = value end +--- Gets a registered ClientEntity entry. +--- @param id ClientEntityId ClientEntity id (key used in ClientEntity.All), not a FiveM entity handle. +--- @return EntityData|nil The registered entry; when spawned, entityData.spawned is the FiveM entity handle. function ClientEntity.Get(id) - return Entities[id] + return ClientEntity.All[id] end +--- Gets all registered ClientEntity entries keyed by ClientEntity id. +--- @return table function ClientEntity.GetAll() - return Entities + return ClientEntity.All end -function ClientEntity.OnCreate(_type, func) - ClientEntity.OnCreates = ClientEntity.OnCreates or {} - if not ClientEntity.OnCreates[_type] then - ClientEntity.OnCreates[_type] = {} +--- Plays or stops an animation on the spawned FiveM entity for a ClientEntity. +--- @param id ClientEntityId ClientEntity id. +--- @param anim ClientEntityAnim|nil Animation data; pass nil to stop. +function ClientEntity.SetAnim(id, anim) + local entityData = ClientEntity.Get(id) + if not entityData then return print(string.format("[ClientEntity] SetAnim: Entity %s does not exist", id)) end + local entity = entityData.spawned + if not entity or not DoesEntityExist(entity) then return end + if not anim then + return Bridge.Anim.Stop(nil, entity) end - table.insert(ClientEntity.OnCreates[_type], func) + Bridge.Anim.Play(nil, entity, anim.dict, anim.name, anim.blendIn or 8.0, anim.blendOut or -8.0, anim.duration, anim.flags, anim.playbackRate or 0.0, anim.onComplete) 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)) +--- Attaches the spawned FiveM entity for a ClientEntity to a target, or detaches when nil. +--- @param id ClientEntityId ClientEntity id. +--- @param attachData ClientEntityAttach|nil Attachment data; pass nil to detach. +function ClientEntity.SetAttach(id, attachData) + local entityData = ClientEntity.Get(id) + if not entityData then return print(string.format("[ClientEntity] Attach: Entity %s does not exist", id)) end + local entity = entityData.spawned + if not attachData then + if not entityData.spawned or not IsEntityAttached(entityData.spawned) then return end + DetachEntity(entityData.spawned, true, true) 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 + local targetEntity = attachData.target + if type(targetEntity) == "string" then + targetEntity = Bridge.Entity.Get(targetEntity)?.spawned or targetEntity + else + local player = GetPlayerFromServerId(targetEntity) + if player and player ~= -1 then + targetEntity = GetPlayerPed(player) + end end - entityData.OnRemove = func + targetEntity = tonumber(targetEntity) + if not targetEntity then return end + local bone = attachData.bone or 0 + local offset = attachData.offset or vector3(0.0, 0.0, 0.0) + local rotation = attachData.rotation or vector3(0.0, 0.0, 0.0) + AttachEntityToEntity(entity, targetEntity, GetPedBoneIndex(targetEntity, bone), offset.x, offset.y, offset.z, rotation.x, rotation.y, rotation.z, true, true, false, true, 1, true) end +--- Adds target options to the spawned FiveM entity for a ClientEntity. +--- @param id ClientEntityId ClientEntity id. +--- @param options table Target config passed to Bridge.Target.AddLocalEntity. +function ClientEntity.SetTargets(id, options) + local entityData = ClientEntity.Get(id) + if not entityData then return end + Bridge.Target.RemoveLocalEntity(entityData.spawned) + entityData.targets = options + if not options then return end + Bridge.Target.AddLocalEntity(entityData.spawned, options) +end + +--- Updates the stored coords for a ClientEntity and updates its proximity trigger. +--- @param id ClientEntityId ClientEntity id. +--- @param coords vector3 New coords. function ClientEntity.UpdateCoords(id, coords) - local entityData = Entities[id] + local entityData = ClientEntity.All[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) + Point.UpdateCoords(id, coords) end +--- Changes the model used by a ClientEntity and respawns it if currently spawned. +--- @param id ClientEntityId ClientEntity id. +--- @param model string|number Model name or hash. function ClientEntity.ChangeModel(id, model) - local entityData = Entities[id] + local entityData = ClientEntity.All[id] if not entityData then return print(string.format("[ClientEntity] ChangeModel: Entity %s does not exist", id)) end entityData.model = model @@ -238,51 +306,78 @@ function ClientEntity.ChangeModel(id, model) SpawnEntity(entityData) end +--- Hooks onto entity creation by checking for a property on the entityData. Includes ServerEntity creations. +--- func = function(entityData) return modifiedEntityData end +--- @param property any property to look for when creating the entity +--- @param func any +function ClientEntity.SetOnCreate(property, func) + ClientEntity.OnCreates[property] = func +end + +--- Sets the OnSpawn callback for a ClientEntity. +--- @param id ClientEntityId ClientEntity id. +--- @param func fun(entityData: EntityData) Called when in-range spawn happens; entityData.spawned is the FiveM entity handle. +function ClientEntity.SetOnSpawn(id, func) + local entityData = ClientEntity.All[id] + if not entityData then + print(string.format("[ClientEntity] SetOnSpawn: Entity %s does not exist", id)) + return + end + entityData.OnSpawn = func +end + +--- Sets the OnRemove callback for a ClientEntity. +--- @param id ClientEntityId ClientEntity id. +--- @param func fun(entityData: EntityData) Called when out-of-range removal happens (and before deleting the spawned entity). +function ClientEntity.SetOnRemove(id, func) + local entityData = ClientEntity.All[id] + if not entityData then + print(string.format("[ClientEntity] SetOnRemove: Entity %s does not exist", id)) + return + end + entityData.OnRemove = func +end -- Network Event Handlers RegisterNetEvent("community_bridge:client:CreateEntity", function(entityData) + local src = source + if src ~= 65535 then return end -- Server only ClientEntity.Create(entityData) end) RegisterNetEvent("community_bridge:client:CreateEntities", function(entities) + local src = source + if src ~= 65535 then return end -- Server only ClientEntity.CreateBulk(entities) end) RegisterNetEvent("community_bridge:client:DeleteEntity", function(id) - ClientEntity.Unregister(id) + local src = source + if src ~= 65535 then return end -- Server only + ClientEntity.Destroy(id) end) RegisterNetEvent("community_bridge:client:UpdateEntity", function(id, data) + local src = source + if src ~= 65535 then return end -- Server only for k, v in pairs(data) do - ClientEntity.Set(id, k, v) + ClientEntity.SetKey(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 + for id, entityData in pairs(ClientEntity.All) do + Point.Remove(id) + RemoveEntity(entityData) end - Entities = {} -- Clear local cache + ClientEntity.All = {} 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 + Point.Remove(id) + RemoveEntity(entityData) + ClientEntity.All[id] = nil end end end) diff --git a/lib/entities/server/server_entity.lua b/lib/entities/server/server_entity.lua index c135432..a629bc3 100644 --- a/lib/entities/server/server_entity.lua +++ b/lib/entities/server/server_entity.lua @@ -1,9 +1,7 @@ Ids = Ids or Require("lib/utility/shared/ids.lua") -Behaviors = Behaviors or Require("lib/entities/shared/behaviors.lua") local Entities = {} ServerEntity = {} -- Renamed from EntityRelay -ServerEntity.Behaviors = Behaviors local Invoked = {} --- Creates a server-side representation of an entity and notifies clients. @@ -32,7 +30,6 @@ end function ServerEntity.Create(data) local self = ServerEntity.New(data.id, data.entityType, data.model, data.coords, data.rotation or vector3(0.0, 0.0, data.heading or 0.0), data) if not self then return nil end - Behaviors.Trigger("OnCreate", self) TriggerClientEvent("community_bridge:client:CreateEntity", -1, self) return self end @@ -43,7 +40,6 @@ function ServerEntity.CreateBulk(entities) local id = entityData.id or Ids.CreateUniqueId(Entities) local entity = ServerEntity.New(id, entityData.entityType, entityData.model, entityData.coords, entityData.rotation, entityData) createdEntities[id] = entity - Behaviors.Trigger("OnCreate", entity) end TriggerClientEvent("community_bridge:client:CreateEntities", -1, createdEntities) return createdEntities @@ -54,7 +50,7 @@ end function ServerEntity.Destroy(id) if not Entities[id] then return end ServerEntity.Remove(id) - TriggerClientEvent("community_bridge:client:DeleteEntity", -1, id) + TriggerClientEvent("community_bridge:client:DeleteEntity", -1, id) end ServerEntity.Delete = ServerEntity.Destroy @@ -91,9 +87,6 @@ function ServerEntity.Set(id, data) return true end -function ServerEntity.RegisterBehavior(id, behavior) - Behaviors.Create(id, behavior) -end -- Clean up entities associated with a stopped resource AddEventHandler('onResourceStop', function(resourceName) diff --git a/lib/entities/shared/behaviors.lua b/lib/entities/shared/behaviors.lua deleted file mode 100644 index 460bb56..0000000 --- a/lib/entities/shared/behaviors.lua +++ /dev/null @@ -1,121 +0,0 @@ -Behaviors = { - All = {}, - isSetup = false, - invoked = {} -} - -Behaviors.init = { - "anim", - "attach", - 'clothing', - 'follow', - 'particles', - 'scenarios', - 'shop', - 'stash', - 'targets', - 'weapon' -} - -function Behaviors.Init() - if Behaviors.isSetup then return end - Behaviors.isSetup = true - for k, v in pairs(Behaviors.init) do - Require(string.format("lib/entities/shared/behaviors/%s.lua", v)) - end -end - -function Behaviors.Create(behaviorId, behavior) - if not behaviorId or not behavior then return end - Behaviors.All = Behaviors.All or {} - local invoking = GetInvokingResource() or "community_bridge" - Behaviors.All[invoking] = Behaviors.All[invoking] or {} - Behaviors.All[invoking][behaviorId] = behavior -end - -function Behaviors.Get(behaviorId) - local invoking = GetInvokingResource() or "community_bridge" - return Behaviors.All[invoking] and Behaviors.All[invoking][behaviorId] -end - -function Behaviors.Remove(behaviorId) - local invoking = GetInvokingResource() or "community_bridge" - if not Behaviors.All[invoking] or not Behaviors.All[invoking][behaviorId] then return end - Behaviors.All[invoking][behaviorId] = nil - return true -end - -function Behaviors.Trigger(actionName, clientEntityData, ...) - if not clientEntityData or not actionName then return end - local invoking = clientEntityData.invoked or GetInvokingResource() or "community_bridge" - for property, behavior in pairs(Behaviors.All[invoking] or {}) do - local hasBehaviorArgs = Behaviors.Has(property, clientEntityData) -- this is everything that's contained inside the object's individual property - if hasBehaviorArgs and behavior[actionName] then - local success, result = pcall(behavior[actionName], clientEntityData, ...) - if not success then - print(string.format("[ClientEntity] Behavior %s failed: %s", property, result)) - end - end - end - for property, behavior in pairs(Behaviors.All['community_bridge'] or {}) do - local hasBehaviorArgs = Behaviors.Has(property, clientEntityData) -- this is everything that's contained inside the object's individual property - if hasBehaviorArgs and behavior[actionName] and not Behaviors.All[invoking]?[actionName] then - local success, result = pcall(behavior[actionName], clientEntityData, ...) - if not success then - print(string.format("[ClientEntity] Behavior %s failed: %s", property, result)) - end - end - end -end - -function Behaviors.Inherit(behaviorId, clientEntityData, defaultData) - local invoking = GetInvokingResource() or "community_bridge" - if not Behaviors.All[invoking] or not Behaviors.All[invoking][behaviorId] then - print(string.format("[ClientEntity] Behavior %s does not exist", behaviorId)) - return false - end - if not clientEntityData or not clientEntityData.id then - print("[ClientEntity] Invalid client entity data provided for inheritance") - return false - end - clientEntityData[behaviorId] = defaultData or {} -- Mark the entity as having this behavior - return true -end - -function Behaviors.Has(behaviorId, clientEntityData) - return clientEntityData and clientEntityData[behaviorId] -end - -function Behaviors.Cleanup(resourceName) - if not resourceName or not Behaviors.All[resourceName] then return end - for _, behaviorId in ipairs(Behaviors.All[resourceName]) do - Behaviors.Remove(behaviorId) - end - Behaviors.All[resourceName] = nil -end - -AddEventHandler("onResourceStop", function(resourceName) - Behaviors.Cleanup(resourceName) - if resourceName ~= GetCurrentResourceName() then return end - for invoking, behaviors in pairs(Behaviors.All) do - for behaviorId, _ in pairs(behaviors) do - Behaviors.Remove(behaviorId) - end - end -end) - -if IsDuplicityVersion() then - RegisterNetEvent("onResourceStart", function(resourceName) - if resourceName ~= GetCurrentResourceName() then return end - Wait(500) - Behaviors.Init() - end) -else - RegisterNetEvent("onClientResourceStart", function(resource) - if resource ~= GetCurrentResourceName() then return end - Wait(500) - Behaviors.Init() - end) -end - -return Behaviors diff --git a/lib/entities/shared/behaviors/anim.lua b/lib/entities/shared/behaviors/anim.lua deleted file mode 100644 index ee6e40a..0000000 --- a/lib/entities/shared/behaviors/anim.lua +++ /dev/null @@ -1,62 +0,0 @@ -if IsDuplicityVersion() then return end -local Animation = { - property = "anim", - default = { - flags = 49, - duration = -1 - } -} --- Anim.Play(id, entity, animDict, animName, blendIn, blendOut, duration, flag, playbackRate, onComplete) -function Animation.Play(entityData) - if entityData.entityType ~= "ped" then return end - local entity = entityData.spawned - if not entity then return end - local dict = entityData.anim?.dict - local name = entityData.anim?.name - local onComplete = entityData.anim?.onComplete and function() entityData.anim.onComplete(entityData) end - if not dict or not name then return end - local flags = entityData.anim.flags or Animation.default.flags - local duration = entityData.anim.duration or Animation.default.duration - entityData.anim.id = Bridge.Anim.Play(nil, entity, dict, name, 8.0, -8.0, duration, flags, 0.0, onComplete) - Bridge.Entity.Set(entityData.id, "anim", entityData.anim) - entityData.oldAnim = entityData.anim - Bridge.Entity.Set(entityData.id, "oldAnim", entityData.oldAnim) - -- SetTimeout(1000, function() - -- if entityData.anim then - -- entityData.anim.disable = false - -- Bridge.Entity.Set(entityData.id, "anim", entityData.anim) - -- end - -- end) -end - -function Animation.OnSpawn(entityData) - print("Spawning animation for entity:", entityData.id) - if not entityData.spawned or not entityData.anim then return end - Animation.Play(entityData) - entityData.anim.disable = false - TaskSetBlockingOfNonTemporaryEvents(entityData.spawned, true) - SetBlockingOfNonTemporaryEvents(entityData.spawned, true) - Bridge.Entity.Set(entityData.id, "anim", entityData.anim) -end - -function Animation.OnRemove(entityData) - if not entityData.spawned or not entityData.anim then return end - entityData.oldAnim = nil - entityData.anim.disable = true - Bridge.Entity.Set(entityData.id, "anim", entityData.anim) - Bridge.Entity.Set(entityData.id, "oldAnim", entityData.oldAnim) -end - -function Animation.OnUpdate(entityData) - if not entityData.spawned or not entityData.anim then return end - if entityData.anim.disable then return end - if not IsEntityPlayingAnim(entityData.spawned, entityData.anim.dict, entityData.anim.name, 3) then - Bridge.Anim.Stop(entityData.anim?.id) - ClearPedTasksImmediately(entityData.spawned) - Wait(100) - Animation.Play(entityData) - end -end - -Bridge.Entity.RegisterBehavior("anim", Animation) -return Animation \ No newline at end of file diff --git a/lib/entities/shared/behaviors/attach.lua b/lib/entities/shared/behaviors/attach.lua deleted file mode 100644 index 8b3f66f..0000000 --- a/lib/entities/shared/behaviors/attach.lua +++ /dev/null @@ -1,52 +0,0 @@ -if IsDuplicityVersion() then return end -local Attach = { - property = "attach", - default = { - bone = 0, - offset = vector3(0.0, 0.0, 0.0), - rotation = vector3(0.0, 0.0, 0.0) - } -} - - -function Attach.OnSpawn(entityData) - if not entityData.spawned or not entityData.attach then return end - local entity = entityData.spawned - local targetEntity = entityData.attach.target - if type(targetEntity) == "string" then - targetEntity = Bridge.Entity.Get(targetEntity)?.spawned or targetEntity - else - local player = GetPlayerFromServerId(targetEntity) - if player and player ~= -1 then - targetEntity = GetPlayerPed(player) - end - end - targetEntity = tonumber(targetEntity) - if not targetEntity then return end - local bone = entityData.attach.bone or Attach.default.bone - local offset = entityData.attach.offset or Attach.default.offset - local rotation = entityData.attach.rotation or Attach.default.rotation - AttachEntityToEntity(entity, targetEntity, GetPedBoneIndex(targetEntity, bone), offset.x, offset.y, offset.z, rotation.x, rotation.y, rotation.z, true, true, false, true, 1, true) - Bridge.Entity.Set(entityData.id, "attach", entityData.attach) -end - -function Attach.Detach(entityData) - if not entityData.spawned or not IsEntityAttached(entityData.spawned) then return end - DetachEntity(entityData.spawned, true, true) - Bridge.Entity.Set(entityData.id, "attach", entityData.attach) -end - -function Attach.OnUpdate(entityData) - if not entityData.spawned then return end - if entityData.attach.disable then - return Attach.Detach(entityData) - end - local entity = entityData.spawned - local coords = GetEntityCoords(entity) - Bridge.Entity.Set(entityData.id, "coords", vector3(coords.x, coords.y, coords.z)) - if IsEntityAttached(entity) then return end - Attach.OnSpawn(entityData) -end - -Bridge.Entity.RegisterBehavior("attach", Attach) -return Attach \ No newline at end of file diff --git a/lib/entities/shared/behaviors/clothing.lua b/lib/entities/shared/behaviors/clothing.lua deleted file mode 100644 index 4ae11db..0000000 --- a/lib/entities/shared/behaviors/clothing.lua +++ /dev/null @@ -1,82 +0,0 @@ -if IsDuplicityVersion() then return end -local Clothing = { - property = "clothing", - default = { - components = {}, - props = {} - } -} - -function Clothing.ChangeClothes(entityData) - if entityData.entityType ~= "ped" then return end - local entity = entityData.spawned - if not entity or not DoesEntityExist(entity) then return end - local clothing = entityData.clothing.target - if clothing then - if type(clothing) == "number" then - local player = GetPlayerFromServerId(clothing) - if player and player ~= -1 then - clothing = Bridge.Clothing.GetAppearance(GetPlayerPed(player)) - end - elseif type(clothing) == "string" then - local targetEntityData = Bridge.Entity.Get(clothing) - if targetEntityData then - clothing = Bridge.Clothing.GetAppearance(targetEntityData.spawned) - end - end - else - clothing = entityData.clothing - end - - Bridge.Clothing.SetAppearance(entity, clothing) - Bridge.Entity.Set(entityData.id, "clothing", clothing) - entityData.oldClothing = clothing - Bridge.Entity.Set(entityData.id, "oldClothing", clothing) - -end - - -function Clothing.OnSpawn(entityData) - if not entityData.spawned or not entityData.clothing then return end - Clothing.ChangeClothes(entityData) - entityData.clothing.updating = false -end - -function Clothing.OnRemove(entityData) - if not entityData.spawned or not entityData.clothing then return end - entityData.oldClothing = nil - entityData.clothing.updating = true -end - -function Clothing.OnUpdate(entityData) - if not entityData.spawned or not entityData.clothing then return end - if entityData.clothing.updating then return end - entityData.clothing.updating = true - if not entityData.oldClothing then - Clothing.ChangeClothes(entityData) - return - end - for componentId, component in pairs(entityData.clothing.components or {}) do - local oldComponent = entityData.oldClothing.components and entityData.oldClothing.components[componentId] - if not oldComponent or oldComponent.drawable ~= component.drawable or oldComponent.texture ~= component.texture then - Clothing.ChangeClothes(entityData) - return - end - end - for propId, prop in pairs(entityData.clothing.props or {}) do - local oldProp = entityData.oldClothing.props and entityData.oldClothing.props[propId] - if not oldProp or oldProp.drawable ~= prop.drawable or oldProp.texture ~= prop.texture then - Clothing.ChangeClothes(entityData) - return - end - end - SetTimeout(1000, function() - if entityData.clothing then - entityData.clothing.updating = false - Bridge.Entity.Set(entityData.id, "clothing", entityData.clothing) - end - end) -end - -Bridge.Entity.RegisterBehavior("clothing", Clothing) -return Clothing \ No newline at end of file diff --git a/lib/entities/shared/behaviors/follow.lua b/lib/entities/shared/behaviors/follow.lua deleted file mode 100644 index addefb3..0000000 --- a/lib/entities/shared/behaviors/follow.lua +++ /dev/null @@ -1,75 +0,0 @@ -if IsDuplicityVersion() then return end -local Follow = { - All = {}, - property = "follow", - default = { - speed = 1.0, - distance = 2.0 - } -} - -function Follow.PedWalkToPos(entityData) - if not entityData?.follow?.target or entityData?.entityType ~= "ped" then return end - local entity = entityData.spawned - local targetCoords = entityData.follow.target - if not targetCoords then return end - if type(targetCoords) == "number" then - local player = GetPlayerFromServerId(targetCoords) - if player and player ~= -1 then - targetCoords = GetEntityCoords(GetPlayerPed(player)) - else - targetCoords = GetEntityCoords(targetCoords) - end - elseif type(targetCoords) == "string" then - local targetEntityData = Bridge.Entity.Get(targetCoords) - if targetEntityData then - targetCoords = GetEntityCoords(targetEntityData.spawned) - end - end - local entityPos = GetEntityCoords(entity) - local distance = #(targetCoords - entityPos) - if distance > (entityData.follow.distance or Follow.default.distance) then - if entityData.anim then - entityData.anim.disable = true - Bridge.Entity.Set(entityData.id, "anim", entityData.anim) - end - if entityData.scenarios then - entityData.scenarios.disable = true - Bridge.Entity.Set(entityData.id, "scenarios", entityData.scenarios) - end - local speed = entityData.follow.speed or Follow.default.speed - local heading = GetHeadingFromVector_2d(targetCoords.x - entityPos.x, targetCoords.y - entityPos.y) - TaskGoStraightToCoord(entity, targetCoords.x, targetCoords.y, targetCoords.z, speed, -1, heading, 0.0) - entityData.coords = GetEntityCoords(entity) - entityData.follow.walking = true - Bridge.Entity.Set(entityData.id, "coords", entityData.coords - vector3(0,0,1)) - Bridge.Entity.Set(entityData.id, "follow", entityData.follow) - if entityData.follow.OnExit then entityData.follow.OnExit(entityData) end - return false - end - if not entityData.follow.walking then return end - entityData.follow.walking = false - Bridge.Entity.Set(entityData.id, "follow", entityData.follow) - if entityData.anim then - entityData.anim.disable = false - Bridge.Entity.Set(entityData.id, "anim", entityData.anim) - end - if entityData.scenarios then - entityData.scenarios.disable = false - Bridge.Entity.Set(entityData.id, "scenarios", entityData.scenarios) - end - ClearPedTasks(entity) - if entityData.follow.OnEnter then entityData.follow.OnEnter(entityData) end - return true -end - - -function Follow.OnUpdate(entityData) - assert(entityData?.id, "Entity ID is required") - local entity = entityData.spawned - if not entity or not DoesEntityExist(entity) then return end - return Follow.PedWalkToPos(entityData) -end - -Bridge.Entity.RegisterBehavior("follow", Follow) -return Follow \ No newline at end of file diff --git a/lib/entities/shared/behaviors/particles.lua b/lib/entities/shared/behaviors/particles.lua deleted file mode 100644 index 3e02b1e..0000000 --- a/lib/entities/shared/behaviors/particles.lua +++ /dev/null @@ -1,94 +0,0 @@ -if IsDuplicityVersion() then return end -Utility = Utility or Bridge.Utility -local ParticlesBehaviors = { - property = "particles", - default = { - dict = "cut_exile3", - ptfx = "cs_ex3_wheel_spin", - offset = vector3(0, 0, 0), - rotation = vector3(0, 0, 0), - size = 1.0, - color = vector3(255, 255, 255), - loopLength = 5000, - } -} - -function ParticlesBehaviors.Start(entityData) - for _, particleData in ipairs(entityData.particles) do - local position = vector3(entityData.coords.x, entityData.coords.y, entityData.coords.z) - local rotation = vector3(entityData.rotation.x, entityData.rotation.y, entityData.rotation.z) - position = position + (particleData.offset?.xyz or vector3(0, 0, 0)) - rotation = rotation + (particleData.rotation?.xyz or vector3(0, 0, 0)) - particleData.playing = Bridge.Particle.Play( - particleData.dict or ParticlesBehaviors.default.dict, - particleData.ptfx or ParticlesBehaviors.default.ptfx, - position, - rotation, - particleData.size or ParticlesBehaviors.default.size, - particleData.color or ParticlesBehaviors.default.color, - particleData.looped, - particleData.loopLength or ParticlesBehaviors.default.loopLength - ) - end -end - -function ParticlesBehaviors.OnSpawn(entityData) - if not entityData.particles then return end - if entityData.particles.dict then -- single particle - entityData.particles = {entityData.particles} - end - ParticlesBehaviors.Start(entityData) -end - - -function ParticlesBehaviors.OnRemove(entityData) - if not entityData.particles then return end - for _, particleData in ipairs(entityData.particles) do - if particleData.playing then - Bridge.Particle.Stop(particleData.playing) - particleData.playing = nil - end - end -end - -function ParticlesBehaviors.OnMove(entityData) - if not entityData.particles then return end - for _, particleData in ipairs(entityData.particles) do - if particleData.playing then - Particle.Stop(particleData.playing) - particleData.playing = nil - local position = vector3(entityData.coords.x, entityData.coords.y, entityData.coords.z) - local rotation = vector3(entityData.rotation.x, entityData.rotation.y, entityData.rotation.z) - position = position + (particleData.offset?.xyz or vector3(0, 0, 0)) - rotation = rotation + (particleData.rotation?.xyz or vector3(0, 0, 0)) - particleData.playing = Bridge.Particle.Play( - particleData.dict or ParticlesBehaviors.default.dict, - particleData.ptfx or ParticlesBehaviors.default.ptfx, - position, - rotation, - particleData.size or ParticlesBehaviors.default.size, - particleData.color or ParticlesBehaviors.default.color, - particleData.looped, - particleData.loopLength or ParticlesBehaviors.default.loopLength - ) - end - end -end - -function ParticlesBehaviors.OnSet(entityData, key, value, oldValue) - if key ~= "particles" then return end - if oldValue then - ParticlesBehaviors.OnRemove(entityData) - end - if value then - if value.dict then -- single particle - entityData.particles = {value} - else - entityData.particles = value - end - ParticlesBehaviors.Start(entityData) - end -end - -Bridge.Entity.RegisterBehavior("particles", ParticlesBehaviors) -return ParticlesBehaviors \ No newline at end of file diff --git a/lib/entities/shared/behaviors/scenarios.lua b/lib/entities/shared/behaviors/scenarios.lua deleted file mode 100644 index 760af6c..0000000 --- a/lib/entities/shared/behaviors/scenarios.lua +++ /dev/null @@ -1,64 +0,0 @@ - -if IsDuplicityVersion() then return end -local Scenarios = { - property = "scenarios", - default = { - introClip = true, - duration = -1 - } -} --- scenarios.Play(id, entity, scenariosDict, scenariosName, blendIn, blendOut, duration, flag, playbackRate, onComplete) -function Scenarios.Play(entityData) - if entityData.entityType ~= "ped" then return end - local entity = entityData.spawned - if not entity then return end - local name = entityData.scenarios?.name - if not name then return end - local duration = entityData.scenarios?.duration or Scenarios.default.duration - local introClip = entityData.scenarios?.introClip or Scenarios.default.introClip - TaskStartScenarioInPlace(entity, name, duration, introClip) - if duration < 0 then return end - SetTimeout(duration, function() - if not entityData.scenarios then return end - ClearPedTasks(entity) - if not entityData.scenarios.onComplete then return end - entityData.scenarios.disabled = true - Bridge.Entity.Set(entityData.id, "scenarios", entityData.scenarios) - entityData.scenarios.onComplete(entityData) - end) - -end - -function Scenarios.OnSpawn(entityData) - if not entityData.spawned or not entityData.scenarios then return end - Scenarios.Play(entityData) -end - -function Scenarios.OnRemove(entityData) - if not entityData.spawned or not entityData.scenarios then return end - ClearPedTasks(entityData.spawned) -end - -function Scenarios.OnUpdate(entityData) - if not entityData.spawned or not entityData.scenarios then return end - if entityData.scenarios.disable then return end - if IsPedUsingScenario(entityData.spawned, entityData.scenarios.name) then return end - local entities = GetGamePool('CObject') - for _, obj in ipairs(entities or {}) do - obj = tonumber(obj) - local entityCoords = GetEntityCoords(obj) - local coords = vector3(entityData.coords.x, entityData.coords.y, entityData.coords.z) - if entityCoords then - local distance = #(entityCoords - coords) - if distance < 2.0 then - SetEntityAsMissionEntity(obj, false, false) - DeleteObject(obj) - break - end - end - end - Scenarios.Play(entityData) -end - -Bridge.Entity.RegisterBehavior("scenarios", Scenarios) -return Scenarios \ No newline at end of file diff --git a/lib/entities/shared/behaviors/shop.lua b/lib/entities/shared/behaviors/shop.lua deleted file mode 100644 index 306752c..0000000 --- a/lib/entities/shared/behaviors/shop.lua +++ /dev/null @@ -1,58 +0,0 @@ -local Shop = { - property = "shop", - default = { - label = "Default Shop Label", - items = { - {item = "water", price = 10} - }, - target = { - label = "Open Shop", - icon = "fa-solid fa-box" - } - } -} - -function Shop.OnCreate(entityData) - local shop = entityData.shop - if not shop then return end - if IsDuplicityVersion() then - local label = shop.label or Shop.default.label - local items = shop.items or Shop.default.items - local coords = entityData.coords - local groups = shop.groups - Bridge.Inventory.RegisterShop(label, items, nil, groups) - return - end - entityData.targets = entityData.targets or {} - print(json.encode(entityData.targets)) - table.insert(entityData.targets, - { - label = shop.target.label or "Shop", - icon = shop.target.icon or "fa-solid fa-box", - onSelect = function() - TriggerServerEvent("community_bridge:server:OpenShop", entityData.id) - end - } - ) -end - -if not IsDuplicityVersion() then return Bridge.Entity.RegisterBehavior("shop", Shop) end - -RegisterNetEvent("community_bridge:server:OpenShop", function(id) - local src = source - if not src then return end - local entityData = Bridge.Entity.Get(id) - if not entityData or not entityData.shop then - return print(string.format("[Shop] OpenShop: Entity %s does not exist or has no shop", id)) - end - local coords = entityData.coords - if not coords then return end - local distance = #(GetEntityCoords(GetPlayerPed(src)) - coords) - if distance > 3.0 then - return print(string.format("[Shop] OpenShop: Player %s is too far from entity %s", src, id)) - end - Bridge.Inventory.OpenShop(src, id) -end) - - -return Shop \ No newline at end of file diff --git a/lib/entities/shared/behaviors/stash.lua b/lib/entities/shared/behaviors/stash.lua deleted file mode 100644 index badbebc..0000000 --- a/lib/entities/shared/behaviors/stash.lua +++ /dev/null @@ -1,61 +0,0 @@ -local Stash = { - property = "stash", - default = { - label = "default_stash_label", - slots = 20, - maxWeight = 100000, - target = { - label = "Open Stash", - icon = "fa-solid fa-box", - } - } -} - -function Stash.OnCreate(entityData) - if not entityData.stash then return end - if IsDuplicityVersion() then - local label = entityData.stash.label or Stash.default.label - local slots = entityData.stash.slots or Stash.default.slots - local weight = entityData.stash.maxWeight or Stash.default.maxWeight - local owner = entityData.stash.owner - local groups = entityData.stash.groups - Bridge.Inventory.RegisterStash(entityData.id, label, slots, weight, owner, groups) - return - end - entityData.targets = entityData.targets or {} - entityData.targets['stash'] = { - label = entityData.stash.target.label or Stash.default.target.label, - icon = entityData.stash.target.icon or Stash.default.target.icon, - description = entityData.stash.target.description, - groups = entityData.stash.target.groups, - canInteract = function(_) - local entData = Bridge.Entity.Get(entityData.id) - return not entData.stash.disable - end, - onSelect = function() - TriggerServerEvent("community_bridge:server:OpenStash", entityData.id) - end - } - - Bridge.Entity.Set(entityData.id, 'targets', entityData.targets) -end - -if not IsDuplicityVersion() then return Bridge.Entity.RegisterBehavior("stash", Stash) end - -RegisterNetEvent("community_bridge:server:OpenStash", function(id) - local src = source - if not src then return end - local entityData = Bridge.Entity.Get(id) - if not entityData or not entityData.stash then - return print(string.format("[Stash] OpenStash: Entity %s does not exist or has no stash", id)) - end - local coords = vector3(entityData.coords.x, entityData.coords.y, entityData.coords.z) - if not coords then return end - local distance = #(GetEntityCoords(GetPlayerPed(src)) - coords) - if distance > 3.0 then - return print(string.format("[Stash] OpenStash: Player %s is too far from entity %s", src, id)) - end - Bridge.Inventory.OpenStash(src, "stash", id) -end) - -Bridge.Entity.RegisterBehavior("stash", Stash) \ No newline at end of file diff --git a/lib/entities/shared/behaviors/targets.lua b/lib/entities/shared/behaviors/targets.lua deleted file mode 100644 index c11afc4..0000000 --- a/lib/entities/shared/behaviors/targets.lua +++ /dev/null @@ -1,78 +0,0 @@ -if IsDuplicityVersion() then return end -local Targets = { - property = "targets", - default = { - label = "Default Target Label", - distance = 2, - size = vector3(2.0, 2.0, 2.0), - } -} - -function Targets.OnSpawn(entityData) - if not entityData.targets then return end - if entityData.targets?.label then - local temp = entityData.targets - entityData.targets = {temp} - end - print("Spawned targets:", entityData.targets) - local numberedTargets = {} - for k, v in pairs(entityData.targets) do - local onSelect = v.onSelect - if onSelect then - v.onSelect = function(entity) - onSelect(entityData, entity) - end - end - table.insert(numberedTargets, v) - end - table.sort(numberedTargets, function(a, b) return (a.label < b.label) end) - if not entityData.spawned then - local size = Targets.default.size - Bridge.Target.AddBoxZone(entityData.id, entityData.coords, entityData.size, entityData.heading, numberedTargets, entityData.debug) - else - Bridge.Target.AddLocalEntity(entityData.spawned, numberedTargets) - end - entityData.oldTargets = entityData.targets - Bridge.Entity.Set(entityData.id, "oldTargets", entityData.oldTargets) -end - -function Targets.OnRemove(entityData) - if not entityData.oldTargets then return end - if not entityData.spawned then - return Bridge.Target.RemoveZone(entityData.id) - end - Bridge.Target.RemoveLocalEntity(entityData.spawned) -end - -function Targets.OnUpdate(entityData) - if not entityData.oldTargets then return end - local doesntMatch = false - for k, v in pairs(entityData.targets) do - if not entityData.oldTargets or not entityData.oldTargets[k] then - doesntMatch = true - break - end - local old = entityData.oldTargets[k] - if old.label ~= v.label - or old.distance ~= v.distance - or old.description ~= v.description - then - doesntMatch = true - break - end - end - - for k, v in pairs(entityData.oldTargets) do - if not entityData.targets or not entityData.targets[k] then - doesntMatch = true - break - end - end - if doesntMatch then - Targets.OnRemove(entityData) - Wait(100) - Targets.OnSpawn(entityData) - end -end -Bridge.Entity.RegisterBehavior("targets", Targets) -return Targets \ No newline at end of file diff --git a/lib/entities/shared/behaviors/weapon.lua b/lib/entities/shared/behaviors/weapon.lua deleted file mode 100644 index 24c6b7c..0000000 --- a/lib/entities/shared/behaviors/weapon.lua +++ /dev/null @@ -1,54 +0,0 @@ -if IsDuplicityVersion() then return end -local Weapon = { - property = "weapon", - default = { - name = "WEAPON_UNARMED", - ammo = 0 - } -} - -function Weapon.GiveWeapon(entityData) - if entityData.entityType ~= "ped" then return end - local entity = entityData.spawned - - if not entity or not DoesEntityExist(entity) then return end - if entityData.weapon?.name then - local ammo = entityData.weapon.ammo or 0 - local hash = GetHashKey(entityData.weapon.name) - GiveWeaponToPed(entity, hash, ammo, false, true) - TaskSwapWeapon(entity, true) - end - entityData.oldWeapon = entityData.weapon - Bridge.Entity.Set(entityData.id, "oldWeapon", entityData.oldWeapon) -end - -function Weapon.OnSpawn(entityData) - if not entityData.spawned or not entityData.weapon then return end - Weapon.GiveWeapon(entityData) - entityData.weapon.updating = false -end - -function Weapon.OnRemove(entityData) - if not entityData.spawned or not entityData.weapon then return end - entityData.oldWeapon = nil - entityData.weapon.updating = true -end - -function Weapon.OnUpdate(entityData) - if not entityData.spawned or not entityData.weapon then return end - if entityData.weapon.updating then return end - entityData.weapon.updating = true - if not entityData.oldWeapon or entityData.oldWeapon.name ~= entityData.weapon.name then - Weapon.GiveWeapon(entityData) - end - SetTimeout(1000, function() - if entityData.weapon then - entityData.weapon.updating = false - Bridge.Entity.Set(entityData.id, "weapon", entityData.weapon) - end - end) - Bridge.Entity.Set(entityData.id, "weapon", entityData.weapon) -end - -Bridge.Entity.RegisterBehavior("weapon", Weapon) -return Weapon \ No newline at end of file From 4bc1925f4d8bde838e30a38cf2d3ae593110e56e Mon Sep 17 00:00:00 2001 From: "Mr.Crowley" Date: Sun, 11 Jan 2026 20:37:42 -0600 Subject: [PATCH 2/3] Add UpdateRotation method to ClientEntity Introduces ClientEntity.UpdateRotation to update entity rotation and apply it to the spawned entity if it exists. Also fixes SetKey to update the key before calling the handler. --- lib/entities/client/client_entity.lua | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/entities/client/client_entity.lua b/lib/entities/client/client_entity.lua index f7601ee..3bbeb76 100644 --- a/lib/entities/client/client_entity.lua +++ b/lib/entities/client/client_entity.lua @@ -203,12 +203,13 @@ function ClientEntity.SetKey(id, key, value) local oldData = entityData[key] local handlerKey = "On" .. key:sub(1, 1):upper() .. key:sub(2) local handler = rawget(entityData, handlerKey) + entityData[key] = value if handler then pcall(function() handler(entityData, key, value, oldData) end) end - entityData[key] = value + end --- Gets a registered ClientEntity entry. @@ -290,6 +291,25 @@ function ClientEntity.UpdateCoords(id, coords) end entityData.coords = coords Point.UpdateCoords(id, coords) + if entityData.spawned and DoesEntityExist(entityData.spawned) then + print(coords.x, coords.y, coords.z) + SetEntityCoords(entityData.spawned, coords.x, coords.y, coords.z, false, false, false, true) + end +end + +function ClientEntity.UpdateRotation(id, rotation) + local entityData = ClientEntity.All[id] + if not entityData then + print(string.format("[ClientEntity] UpdateRotation: Entity %s does not exist", id)) + return + end + if type(rotation) == "number" or type(rotation) == "string" then + rotation = vector3(0.0, 0.0, tonumber(rotation)) + end + entityData.rotation = rotation + if entityData.spawned and DoesEntityExist(entityData.spawned) then + SetEntityRotation(entityData.spawned, rotation.x, rotation.y, rotation.z, 2, true) + end end --- Changes the model used by a ClientEntity and respawns it if currently spawned. From 1f2519af5b92c7f3e7a9de1cc28bb5969f9ec895 Mon Sep 17 00:00:00 2001 From: "Mr.Crowley" Date: Mon, 12 Jan 2026 16:42:44 -0600 Subject: [PATCH 3/3] took pr suggestions removed print and added missed documentation also missed passing id for anim, id is based off entitydata id --- lib/entities/client/client_entity.lua | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/entities/client/client_entity.lua b/lib/entities/client/client_entity.lua index 3bbeb76..a61517a 100644 --- a/lib/entities/client/client_entity.lua +++ b/lib/entities/client/client_entity.lua @@ -5,7 +5,7 @@ ClientEntity = { All = {}, Invoked = {}, OnCreates = {} -} -- Renamed from BaseEntity +} ---------------------------------------- ---@alias ClientEntityId string|number @@ -44,7 +44,9 @@ ClientEntity = { ---@field OnRemove fun(entityData: EntityData)|nil ---@field spawned EntityHandle|nil FiveM entity handle created when the entry is in-range. --- Set the entity and apply any relevant properties such as freezing and targets. +--- Set the entity and apply any relevant properties such as freezing and targets +--- @param id The entities id +--- @return nil local function SetEntityProperties(id) local entityData = ClientEntity.Get(id) if not entityData then return end @@ -58,9 +60,6 @@ local function SetEntityProperties(id) SetEntityInvincible(entity, entityData.invincible) end if entityData.targets then - for k, v in pairs(entityData.targets) do - print(v.label) - end ClientEntity.SetTargets(id, entityData.targets) end if entityData.anim then @@ -71,6 +70,8 @@ local function SetEntityProperties(id) end end +--- Spawns an entity +--- @param entityData EntityData local function SpawnEntity(entityData) if entityData.spawned and DoesEntityExist(entityData.spawned) then return end -- Already spawned if entityData.model then @@ -104,6 +105,8 @@ local function SpawnEntity(entityData) end) end +--- Removes an entity +--- @param entityData EntityData local function RemoveEntity(entityData) entityData = entityData and entityData.args or entityData if not entityData then return end @@ -142,7 +145,6 @@ function ClientEntity.Create(entityData) end end - local entityPoint = Point.Register(entityData.id, entityData.coords, entityData.spawnDistance or 50.0, entityData, SpawnEntity, RemoveEntity, UpdateEntity) ClientEntity.All[entityData.id] = entityPoint ClientEntity.Invoked[entityData.invoked] = ClientEntity.Invoked[entityData.invoked] or {} @@ -233,10 +235,8 @@ function ClientEntity.SetAnim(id, anim) if not entityData then return print(string.format("[ClientEntity] SetAnim: Entity %s does not exist", id)) end local entity = entityData.spawned if not entity or not DoesEntityExist(entity) then return end - if not anim then - return Bridge.Anim.Stop(nil, entity) - end - Bridge.Anim.Play(nil, entity, anim.dict, anim.name, anim.blendIn or 8.0, anim.blendOut or -8.0, anim.duration, anim.flags, anim.playbackRate or 0.0, anim.onComplete) + if not anim then return Bridge.Anim.Stop(id) end + Bridge.Anim.Play(id, entity, anim.dict, anim.name, anim.blendIn or 8.0, anim.blendOut or -8.0, anim.duration, anim.flags, anim.playbackRate or 0.0, anim.onComplete) end --- Attaches the spawned FiveM entity for a ClientEntity to a target, or detaches when nil. @@ -292,11 +292,14 @@ function ClientEntity.UpdateCoords(id, coords) entityData.coords = coords Point.UpdateCoords(id, coords) if entityData.spawned and DoesEntityExist(entityData.spawned) then - print(coords.x, coords.y, coords.z) SetEntityCoords(entityData.spawned, coords.x, coords.y, coords.z, false, false, false, true) end end +--- Updates the rotation for a registered entity +--- @param id ClientEntityId ClientEntity id. +--- @param rotation number|string +--- @return nil function ClientEntity.UpdateRotation(id, rotation) local entityData = ClientEntity.All[id] if not entityData then