2025-04-23 14:10:19 -05:00
|
|
|
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")
|
2025-04-28 22:51:54 -05:00
|
|
|
ClientEntityActions = ClientEntityActions or Require("lib/entities/client/client_entity_actions_ext.lua") -- Added
|
2025-04-23 14:10:19 -05:00
|
|
|
|
|
|
|
|
local Entities = {} -- Stores entity data received from server
|
|
|
|
|
ClientEntity = {} -- Renamed from BaseEntity
|
|
|
|
|
|
|
|
|
|
local function SpawnEntity(entityData)
|
|
|
|
|
entityData = entityData and entityData.args
|
|
|
|
|
if entityData.spawned and DoesEntityExist(entityData.spawned) then return end -- Already spawned
|
|
|
|
|
-- for k, v in pairs(entityData.args) do
|
|
|
|
|
-- print(string.format("SpawnEntity %s: %s", k, v))
|
|
|
|
|
-- end
|
|
|
|
|
local model = entityData.model and type(entityData.model) == 'string' and GetHashKey(entityData.model) or entityData.model
|
|
|
|
|
if not Utility.LoadModel(model) then
|
|
|
|
|
print(string.format("[ClientEntity] Failed to load model %s for entity %s", entityData.model, entityData.id))
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local entity = nil
|
|
|
|
|
local coords = entityData.coords
|
|
|
|
|
local rotation = entityData.rotation
|
|
|
|
|
|
|
|
|
|
if entityData.entityType == 'object' then
|
|
|
|
|
entity = CreateObject(model, coords.x, coords.y, coords.z, false, false, false)
|
|
|
|
|
SetEntityRotation(entity, rotation.x, rotation.y, rotation.z, 2, true)
|
|
|
|
|
elseif entityData.entityType == 'ped' then
|
|
|
|
|
entity = CreatePed(4, model, coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or rotation.z, false, false)
|
|
|
|
|
-- Apply ped specific settings from entityData.meta if needed
|
|
|
|
|
elseif entityData.entityType == 'vehicle' then
|
|
|
|
|
entity = CreateVehicle(model, coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or rotation.z, false, false)
|
|
|
|
|
-- Apply vehicle specific settings from entityData.meta if needed
|
|
|
|
|
else
|
|
|
|
|
print(string.format("[ClientEntity] Unknown entity type '%s' for entity %s", entityData.entityType, entityData.id))
|
|
|
|
|
end
|
|
|
|
|
if entity then
|
|
|
|
|
entityData.spawned = entity
|
|
|
|
|
SetModelAsNoLongerNeeded(model)
|
|
|
|
|
SetEntityAsMissionEntity(entity, true, true) -- Keep entity from being deleted by game engine
|
|
|
|
|
FreezeEntityPosition(entity, entityData.freeze or false) -- Optional freeze based on meta
|
|
|
|
|
if entityData.OnSpawn and type(entityData.OnSpawn) == 'function' then
|
|
|
|
|
entityData.OnSpawn(entityData)
|
|
|
|
|
end
|
|
|
|
|
-- print(string.format("[ClientEntity] Spawned %s entity %s (GameID: %s)", entityData.entityType, entityData.id, entity))
|
|
|
|
|
else
|
|
|
|
|
SetModelAsNoLongerNeeded(model)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function RemoveEntity(entityData)
|
|
|
|
|
entityData = entityData and entityData.args
|
|
|
|
|
if not entityData then return end -- Added safety check
|
|
|
|
|
|
2025-04-28 22:51:54 -05:00
|
|
|
ClientEntityActions.StopAction(entityData.id) -- Stop any ongoing action AND clear queue before despawning
|
2025-04-23 14:10:19 -05:00
|
|
|
|
|
|
|
|
if entityData.spawned and DoesEntityExist(entityData.spawned) then
|
|
|
|
|
local entityHandle = entityData.spawned
|
|
|
|
|
entityData.spawned = nil -- Clear handle first
|
|
|
|
|
SetEntityAsMissionEntity(entityHandle, false, false) -- Allow game engine to delete if needed, though we delete manually
|
|
|
|
|
DeleteEntity(entityHandle)
|
|
|
|
|
-- print(string.format("[ClientEntity] Removed %s entity %s (GameID: %s)", entityData.entityType, entityData.id, entityHandle))
|
|
|
|
|
end
|
|
|
|
|
if entityData.OnRemove and type(entityData.OnRemove) == 'function' then
|
|
|
|
|
entityData.OnRemove(entityData)
|
|
|
|
|
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.Register(entityData)
|
|
|
|
|
if Entities[entityData.id] then return end -- Already registered
|
|
|
|
|
|
|
|
|
|
Entities[entityData.id] = entityData
|
|
|
|
|
-- print(string.format("[ClientEntity] Registering %s entity %s", entityData.entityType, entityData.id))
|
|
|
|
|
|
|
|
|
|
-- Use Point system for proximity checks
|
2025-04-28 22:51:54 -05:00
|
|
|
print(string.format("[ClientEntity] Registering entity %s at %s", entityData.id, json.encode(entityData)))
|
2025-04-23 14:10:19 -05:00
|
|
|
Point.Register(entityData.id, entityData.coords, entityData.spawnDistance or 50.0, entityData, SpawnEntity, RemoveEntity, function() end)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- Unregisters an entity and removes it from the world if spawned.
|
|
|
|
|
-- @param id string|number The ID of the entity to unregister.
|
|
|
|
|
function ClientEntity.Unregister(id)
|
|
|
|
|
local entityData = Entities[id]
|
|
|
|
|
if not entityData then return end
|
|
|
|
|
|
|
|
|
|
-- print(string.format("[ClientEntity] Unregistering entity %s", id))
|
|
|
|
|
Point.Remove(id) -- Remove from proximity checks (this will call RemoveEntity if currently in range)
|
|
|
|
|
RemoveEntity(entityData) -- Ensure it's removed even if Point.Remove didn't trigger it
|
|
|
|
|
Entities[id] = nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- Updates the data for a registered entity.
|
|
|
|
|
-- @param id string|number The ID of the entity to update.
|
|
|
|
|
-- @param data table The data fields to update.
|
|
|
|
|
function ClientEntity.Update(id, data)
|
|
|
|
|
local entityData = Entities[id]
|
|
|
|
|
print(string.format("[ClientEntity] Updating entity %s", id))
|
|
|
|
|
if not entityData then return end
|
|
|
|
|
|
|
|
|
|
local needsPointUpdate = false
|
|
|
|
|
for key, value in pairs(data) do
|
|
|
|
|
if key == 'coords' and #(entityData.coords - value) > 0.1 then
|
|
|
|
|
print(string.format("[ClientEntity] Updating coords for entity %s", id))
|
|
|
|
|
needsPointUpdate = true
|
|
|
|
|
end
|
|
|
|
|
if key == 'spawnDistance' and entityData.spawnDistance ~= value then
|
|
|
|
|
needsPointUpdate = true
|
|
|
|
|
end
|
|
|
|
|
entityData[key] = value
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- If entity is currently spawned, apply updates
|
|
|
|
|
if entityData.spawned and DoesEntityExist(entityData.spawned) then
|
|
|
|
|
if data.coords then
|
|
|
|
|
SetEntityCoords(entityData.spawned, entityData.coords.x, entityData.coords.y, entityData.coords.z, false, false, false, true)
|
|
|
|
|
end
|
|
|
|
|
if data.rotation then
|
|
|
|
|
if entityData.entityType == 'object' then
|
|
|
|
|
SetEntityRotation(entityData.spawned, entityData.rotation.x, entityData.rotation.y, entityData.rotation.z, 2, true)
|
|
|
|
|
else -- Ped/Vehicle heading
|
|
|
|
|
SetEntityHeading(entityData.spawned, type(entityData.rotation) == 'number' and entityData.rotation or entityData.rotation.z)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if data.freeze ~= nil then
|
|
|
|
|
FreezeEntityPosition(entityData.spawned, data.freeze)
|
|
|
|
|
end
|
|
|
|
|
-- Add other updatable properties as needed
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Update Point registration if coords or distance changed
|
|
|
|
|
if needsPointUpdate then
|
|
|
|
|
Point.Remove(id)
|
|
|
|
|
Point.Register(
|
|
|
|
|
entityData.id,
|
|
|
|
|
entityData.coords,
|
|
|
|
|
entityData.spawnDistance or 50.0,
|
|
|
|
|
SpawnEntity,
|
|
|
|
|
RemoveEntity,
|
|
|
|
|
nil,
|
|
|
|
|
entityData
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if entityData.OnUpdate and type(entityData.OnUpdate) == 'function' then
|
|
|
|
|
entityData.OnUpdate(entityData, data)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function ClientEntity.Get(id)
|
|
|
|
|
return Entities[id]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function ClientEntity.GetAll()
|
|
|
|
|
return Entities
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Network Event Handlers
|
|
|
|
|
RegisterNetEvent("community_bridge:client:CreateEntity", function(entityData)
|
|
|
|
|
ClientEntity.Register(entityData)
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
RegisterNetEvent("community_bridge:client:DeleteEntity", function(id)
|
|
|
|
|
ClientEntity.Unregister(id)
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
RegisterNetEvent("community_bridge:client:UpdateEntity", function(id, data)
|
|
|
|
|
ClientEntity.Update(id, data)
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
-- New handler for entity actions
|
|
|
|
|
RegisterNetEvent("community_bridge:client:TriggerEntityAction", function(entityId, actionName, ...)
|
|
|
|
|
local entityData = Entities[entityId]
|
2025-04-28 22:51:54 -05:00
|
|
|
print("here", json.encode(entityData))
|
|
|
|
|
-- Check if entity exists locally (it doesn't need to be spawned to queue actions)
|
|
|
|
|
if entityData then
|
|
|
|
|
if actionName == "Stop" then
|
|
|
|
|
ClientEntityActions.StopAction(entityId)
|
|
|
|
|
elseif actionName == "Skip" then
|
|
|
|
|
ClientEntityActions.SkipAction(entityId)
|
2025-04-23 14:10:19 -05:00
|
|
|
else
|
2025-04-28 22:51:54 -05:00
|
|
|
print(string.format("[ClientEntity] Triggering action '%s' for entity %s", actionName, entityId))
|
|
|
|
|
local currentAction = ClientEntityActions.ActionQueue[entityId] and ClientEntityActions.ActionQueue[entityId][1]
|
|
|
|
|
ClientEntityActions.QueueAction(entityData, actionName, ...)
|
2025-04-23 14:10:19 -05:00
|
|
|
end
|
|
|
|
|
-- else
|
2025-04-28 22:51:54 -05:00
|
|
|
-- Optional: Log if action received but entity doesn't exist locally at all
|
|
|
|
|
-- print(string.format("[ClientEntity] Received action '%s' for non-existent entity %s.", actionName, entityId))
|
2025-04-23 14:10:19 -05:00
|
|
|
end
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
-- Resource Stop Cleanup
|
|
|
|
|
AddEventHandler('onResourceStop', function(resourceName)
|
|
|
|
|
if resourceName == GetCurrentResourceName() then
|
|
|
|
|
for id, entityData in pairs(Entities) do
|
|
|
|
|
Point.Remove(id) -- Clean up point registration
|
|
|
|
|
RemoveEntity(entityData) -- Clean up spawned game entity
|
|
|
|
|
end
|
|
|
|
|
Entities = {} -- Clear local cache
|
|
|
|
|
end
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
return ClientEntity
|