Merge pull request #176 from TheOrderFivem/behavior-replacement

Stripped away inefficient behavior system
This commit is contained in:
MrNewb authored and GitHub committed 2026-01-24 18:00:39 -05:00
commit 80dd16efa8
13 files changed
+265 -955

No files matched your search

+264 -146
View File
@@ -1,18 +1,80 @@
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
All = {},
Invoked = {},
OnCreates = {}
}
----------------------------------------
--------------------
---@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
--- @param id The entities id
--- @return nil
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
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
--- 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
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 +95,21 @@ 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
--- Removes an entity
--- @param entityData EntityData
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 +123,53 @@ 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<number, EntityData> List of ClientEntity definitions.
--- @return table<ClientEntityId, table> Map of ClientEntity id to registered entries.
function ClientEntity.CreateBulk(entities)
local registeredEntities = {}
for _, entityData in pairs(entities) do
@@ -139,96 +178,148 @@ 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<Key> 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)
entityData[key] = value
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
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<ClientEntityId, EntityData>
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] = {}
end
table.insert(ClientEntity.OnCreates[_type], func)
--- 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(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
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)
if entityData.spawned and DoesEntityExist(entityData.spawned) then
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
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.
--- @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 +329,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)
+1 -8
View File
@@ -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)
-121
View File
@@ -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
-62
View File
@@ -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
-52
View File
@@ -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
@@ -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
-75
View File
@@ -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
@@ -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
@@ -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
-58
View File
@@ -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
-61
View File
@@ -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)
-78
View File
@@ -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
-54
View File
@@ -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