ed
This commit is contained in:
parent
510e3ffcf2
commit
f43cf424cf
305 changed files with 34683 additions and 0 deletions
|
@ -0,0 +1,248 @@
|
|||
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")
|
||||
ClientEntityActions = ClientEntityActions or Require("lib/entities/client/client_entity_actions_ext.lua") -- Added
|
||||
|
||||
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 model and 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 or vector3(0.0, 0.0, 0.0) -- Default rotation if not provided
|
||||
|
||||
if entityData.entityType == 'object' then
|
||||
entity = CreateObject(model, coords.x, coords.y, coords.z, false, false, false)
|
||||
SetEntityRotation(entity, rotation.x, rotation.y, rotation.z, 2, true)
|
||||
elseif entityData.entityType == 'ped' then
|
||||
entity = CreatePed(4, model, coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or rotation.z, false, false)
|
||||
elseif entityData.entityType == 'vehicle' then
|
||||
entity = CreateVehicle(model, coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or rotation.z, false, false)
|
||||
else
|
||||
print(string.format("[ClientEntity] Unknown entity type '%s' for entity %s", entityData.entityType, entityData.id))
|
||||
end
|
||||
if entity and model then
|
||||
entityData.spawned = entity
|
||||
SetModelAsNoLongerNeeded(model)
|
||||
SetEntityAsMissionEntity(entity, true, true)
|
||||
FreezeEntityPosition(entity, true)
|
||||
else
|
||||
SetModelAsNoLongerNeeded(model)
|
||||
end
|
||||
if entityData.OnSpawn then
|
||||
entityData.OnSpawn(entityData)
|
||||
end
|
||||
end
|
||||
|
||||
local function RemoveEntity(entityData)
|
||||
entityData = entityData and entityData.args or entityData
|
||||
if not entityData then return end
|
||||
ClientEntityActions.StopAction(entityData.id)
|
||||
if entityData.spawned and DoesEntityExist(entityData.spawned) then
|
||||
local entityHandle = entityData.spawned
|
||||
entityData.spawned = nil
|
||||
SetEntityAsMissionEntity(entityHandle, false, false)
|
||||
DeleteEntity(entityHandle)
|
||||
end
|
||||
if entityData.OnRemove 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.Create(entityData)
|
||||
entityData.id = entityData.id or Ids.CreateUniqueId(Entities)
|
||||
if Entities[entityData.id] then return Entities[entityData.id] end -- Already registered
|
||||
Entities[entityData.id] = entityData
|
||||
return Point.Register(entityData.id, entityData.coords, entityData.spawnDistance or 50.0, entityData, SpawnEntity, RemoveEntity, function() end)
|
||||
end
|
||||
|
||||
--Depricated use ClientEntity.Create instead
|
||||
--- Registers an entity and spawns it in the world if not already spawned.
|
||||
ClientEntity.Register = ClientEntity.Create
|
||||
|
||||
|
||||
function ClientEntity.CreateBulk(entities)
|
||||
local registeredEntities = {}
|
||||
for _, entityData in pairs(entities) do
|
||||
local entity = ClientEntity.Create(entityData)
|
||||
registeredEntities[entity.id] = entity
|
||||
end
|
||||
return registeredEntities
|
||||
end
|
||||
|
||||
-- Depricated use ClientEntity.CreateBulk instead
|
||||
ClientEntity.RegisterBulk = ClientEntity.CreateBulk
|
||||
|
||||
|
||||
|
||||
--- Unregisters an entity and removes it from the world if spawned.
|
||||
-- @param id string|number The ID of the entity to unregister.
|
||||
function ClientEntity.Destroy(id)
|
||||
local entityData = Entities[id]
|
||||
if not entityData then return end
|
||||
|
||||
Point.Remove(id)
|
||||
RemoveEntity(entityData)
|
||||
Entities[id] = nil
|
||||
end
|
||||
ClientEntity.Unregister = ClientEntity.Destroy
|
||||
|
||||
|
||||
--- 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
|
||||
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
|
||||
|
||||
function ClientEntity.RegisterAction(name, func)
|
||||
ClientEntityActions.RegisterAction(name, func)
|
||||
end
|
||||
|
||||
function ClientEntity.OnCreate(_type, func)
|
||||
ClientEntity.OnCreates = ClientEntity.OnCreates or {}
|
||||
if not ClientEntity.OnCreates[_type] then
|
||||
ClientEntity.OnCreates[_type] = {}
|
||||
end
|
||||
table.insert(ClientEntity.OnCreates[_type], func)
|
||||
end
|
||||
|
||||
-- Network Event Handlers
|
||||
RegisterNetEvent("community_bridge:client:CreateEntity", function(entityData)
|
||||
ClientEntity.Create(entityData)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("community_bridge:client:CreateEntities", function(entities)
|
||||
ClientEntity.CreateBulk(entities)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("community_bridge:client:DeleteEntity", function(id)
|
||||
ClientEntity.Unregister(id)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("community_bridge:client:UpdateEntity", function(id, data)
|
||||
ClientEntity.Update(id, data)
|
||||
end)
|
||||
|
||||
-- New handler for entity actions
|
||||
RegisterNetEvent("community_bridge:client:TriggerEntityAction", function(entityId, actionName, ...)
|
||||
local entityData = Entities[entityId]
|
||||
-- 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)
|
||||
else
|
||||
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, ...)
|
||||
end
|
||||
-- else
|
||||
-- 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))
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("community_bridge:client:TriggerEntityActions", function(entityId, actions, endPosition)
|
||||
local entityData = Entities[entityId]
|
||||
if entityData then
|
||||
for _, actionData in pairs(actions) do
|
||||
local actionName = actionData.name
|
||||
local actionParams = actionData.params
|
||||
if actionName == "Stop" then
|
||||
ClientEntityActions.StopAction(entityId)
|
||||
elseif actionName == "Skip" then
|
||||
ClientEntityActions.SkipAction(entityId)
|
||||
else
|
||||
local currentAction = ClientEntityActions.ActionQueue[entityId] and ClientEntityActions.ActionQueue[entityId][1]
|
||||
ClientEntityActions.QueueAction(entityData, actionName, table.unpack(actionParams))
|
||||
end
|
||||
end
|
||||
else
|
||||
print(string.format("[ClientEntity] Received actions for non-existent entity %s.", entityId))
|
||||
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
|
|
@ -0,0 +1,136 @@
|
|||
|
||||
|
||||
ClientEntityActions = {}
|
||||
ClientEntityActions.ActionThreads = {} -- Store running action threads { [entityId] = thread }
|
||||
ClientEntityActions.ActionQueue = {} -- Stores pending actions { [entityId] = {{name="ActionName", args={...}}, ...} }
|
||||
ClientEntityActions.IsActionRunning = {} -- Tracks if an action is currently running { [entityId] = boolean }
|
||||
ClientEntityActions.RegisteredActions = {} -- New: Registry for action implementations { [actionName] = function(entityData, ...) }
|
||||
-- Forward declaration
|
||||
|
||||
|
||||
--- Processes the next action in the queue for a given entity.
|
||||
-- @param entityId string|number The ID of the entity.
|
||||
function ClientEntityActions.ProcessNextAction(entityId)
|
||||
if ClientEntityActions.IsActionRunning [entityId] then return end -- Already running something
|
||||
local queue = ClientEntityActions.ActionQueue[entityId]
|
||||
if not queue or #queue == 0 then return end -- Queue is empty
|
||||
|
||||
local nextAction = table.remove(queue, 1) -- Dequeue (FIFO)
|
||||
local entityData = ClientEntity.Get(entityId) -- Assumes ClientEntity is accessible
|
||||
-- Check if entity is still valid and spawned before starting next action
|
||||
if not entityData or not entityData.spawned or not DoesEntityExist(entityData.spawned) then
|
||||
-- Entity despawned while idle, clear queue and do nothing
|
||||
ClientEntityActions.ActionQueue[entityId] = nil
|
||||
return
|
||||
end
|
||||
|
||||
-- Look up the action in the registry
|
||||
local actionFunc = ClientEntityActions.RegisteredActions [nextAction.name]
|
||||
if actionFunc then
|
||||
-- print(string.format("[ClientEntityActions] Starting action '%s' for entity %s", nextAction.name, entityId))
|
||||
ClientEntityActions.IsActionRunning [entityId] = true
|
||||
-- Call the registered function
|
||||
ClientEntityActions.ActionQueue[entityId] = actionFunc(entityData, table.unpack(nextAction.args))
|
||||
if not ClientEntityActions.ActionQueue[entityId] then
|
||||
-- If the action function doesn't return a queue, set it to nil
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
end
|
||||
else
|
||||
print(string.format("[ClientEntityActions] Unknown action '%s' dequeued for entity %s", nextAction.name, entityId))
|
||||
-- Skip unknown action and try the next one immediately
|
||||
ClientEntityActions.ActionQueue[entityId] = ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
end
|
||||
--- Registers a custom action implementation.
|
||||
-- The action function should handle its own logic, including threading if needed,
|
||||
-- and MUST call ClientEntityActions.ProcessNextAction(entityData.id) when it completes or fails,
|
||||
-- after setting ClientEntityActions.IsActionRunning [entityData.id] = false.
|
||||
-- @param actionName string The name used to trigger this action.
|
||||
-- @param actionFunc function The function to execute. Signature: function(entityData, ...)
|
||||
function ClientEntityActions.RegisterAction(actionName, actionFunc)
|
||||
if ClientEntityActions.RegisteredActions [actionName] then
|
||||
print(string.format("[ClientEntityActions] WARNING: Overwriting registered action '%s'", actionName))
|
||||
end
|
||||
assert(type(actionName) == "string", "actionName must be a string")
|
||||
ClientEntityActions.RegisteredActions[actionName] = actionFunc
|
||||
-- print(string.format("[ClientEntityActions] Registered action: %s", actionName))
|
||||
end
|
||||
|
||||
--- Queues an action for an entity. Starts processing if idle.
|
||||
-- @param entityData table The entity data.
|
||||
-- @param actionName string The name of the action.
|
||||
-- @param ... any Arguments for the action.
|
||||
function ClientEntityActions.QueueAction(entityData, actionName, ...)
|
||||
local entityId = entityData.id
|
||||
if not ClientEntityActions.ActionQueue[entityId] then
|
||||
ClientEntityActions.ActionQueue[entityId] = {}
|
||||
end
|
||||
|
||||
local actionArgs = {...}
|
||||
table.insert(ClientEntityActions.ActionQueue[entityId], { name = actionName, args = actionArgs })
|
||||
-- print(string.format("[ClientEntityActions] Queued action '%s' for entity %s. Queue size: %d", actionName, entityId, #ClientEntityActions.ActionQueue[entityId]))
|
||||
|
||||
-- If the entity isn't currently doing anything, start processing immediately
|
||||
if not ClientEntityActions.IsActionRunning [entityId] then
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
end
|
||||
--- Stops the current action and clears the queue for a specific entity.
|
||||
-- @param entityId string|number The ID of the entity.
|
||||
function ClientEntityActions.StopAction(entityId)
|
||||
-- print(string.format("[ClientEntityActions] Stopping all actions for entity %s", entityId))
|
||||
ClientEntityActions.ActionQueue[entityId] = nil -- Clear the queue
|
||||
ClientEntityActions.IsActionRunning [entityId] = false -- Mark as not running (this will stop loops in threads)
|
||||
|
||||
-- Stop current task/thread if applicable
|
||||
if ClientEntityActions.ActionThreads[entityId] then
|
||||
-- Lua threads stop themselves based on ClientEntityActions.IsActionRunning flag
|
||||
ClientEntityActions.ActionThreads[entityId] = nil
|
||||
end
|
||||
|
||||
-- Specific task clearing for peds
|
||||
local entityData = ClientEntity.Get(entityId)
|
||||
if entityData and entityData.spawned and DoesEntityExist(entityData.spawned) then
|
||||
if IsEntityAPed(entityData.spawned) then
|
||||
ClearPedTasksImmediately(entityData.spawned) -- Use Immediately for forceful stop
|
||||
end
|
||||
-- Other entity types might need different stop logic
|
||||
end
|
||||
end
|
||||
--- Skips the current action and starts the next one in the queue, if any.
|
||||
-- @param entityId string|number The ID of the entity.
|
||||
function ClientEntityActions.SkipAction(entityId)
|
||||
if not ClientEntityActions.IsActionRunning [entityId] then
|
||||
-- print(string.format("[ClientEntityActions] SkipAction called for %s, but no action running.", entityId))
|
||||
return -- Nothing to skip
|
||||
end
|
||||
-- print(string.format("[ClientEntityActions] Skipping current action for entity %s", entityId))
|
||||
|
||||
ClientEntityActions.IsActionRunning [entityId] = false -- Mark as not running (this will stop loops in threads)
|
||||
|
||||
-- Stop current task/thread if applicable
|
||||
if ClientEntityActions.ActionThreads[entityId] then
|
||||
ClientEntityActions.ActionThreads[entityId] = nil
|
||||
end
|
||||
|
||||
local entityData = ClientEntity.Get(entityId)
|
||||
if entityData and entityData.spawned and DoesEntityExist(entityData.spawned) then
|
||||
if IsEntityAPed(entityData.spawned) then
|
||||
ClearPedTasksImmediately(entityData.spawned)
|
||||
end
|
||||
end
|
||||
|
||||
-- Immediately try to process the next action
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
-- Add server-callable functions for Stop and Skip
|
||||
function ClientEntityActions.Stop(entityData)
|
||||
ClientEntityActions.StopAction(entityData.id)
|
||||
end
|
||||
|
||||
function ClientEntityActions.Skip(entityData)
|
||||
ClientEntityActions.SkipAction(entityData.id)
|
||||
end
|
||||
|
||||
return ClientEntityActions
|
|
@ -0,0 +1,359 @@
|
|||
DefaultActions = {}
|
||||
ClientEntityActions = ClientEntityActions or Require("lib/entities/client/client_entity_actions.lua")
|
||||
LA = LA or Require("lib/utility/shared/la.lua")
|
||||
|
||||
--- Internal implementation for walking. Registered via RegisterAction.
|
||||
function DefaultActions.WalkTo(entityData, coords, speed, timeout)
|
||||
local entity = entityData.spawned
|
||||
local entityId = entityData.id -- Store ID locally for safety in thread
|
||||
|
||||
if not entity or not DoesEntityExist(entity) or not IsEntityAPed(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
return
|
||||
end
|
||||
|
||||
-- Clear previous tasks just in case
|
||||
ClearPedTasks(entity)
|
||||
|
||||
local thread = CreateThread(function()
|
||||
TaskGoToCoordAnyMeans(entity, coords.x, coords.y, coords.z, speed or 1.0, 0, false, 786603, timeout or -1)
|
||||
-- Wait until task is completed/interrupted or entity is despawned/changed
|
||||
local entityCoords = GetEntityCoords(entity)
|
||||
while ClientEntityActions.IsActionRunning[entityId] and entityData.spawned == entity and DoesEntityExist(entity) and #(entityCoords - coords) > 2.0 do
|
||||
entityCoords = GetEntityCoords(entity)
|
||||
Wait(0) -- Yield to avoid freezing the game
|
||||
end
|
||||
ClientEntityActions.ActionThreads[entityId] = nil -- Clear thread reference
|
||||
-- Only process next action if this thread was the one running the action
|
||||
if ClientEntityActions.IsActionRunning[entityId] then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
end)
|
||||
ClientEntityActions.ActionThreads[entityId] = thread
|
||||
end
|
||||
--- Internal implementation for playing an animation. Registered via RegisterAction.
|
||||
--- @param entityData table
|
||||
--- @param animDict string
|
||||
--- @param animName string
|
||||
--- @param blendIn number (Optional, default 8.0)
|
||||
--- @param blendOut number (Optional, default -8.0)
|
||||
--- @param duration number (Optional, default -1 for loop/until stopped)
|
||||
--- @param flag number (Optional, default 0)
|
||||
--- @param playbackRate number (Optional, default 0.0)
|
||||
function DefaultActions.PlayAnim(entityData, animDict, animName, blendIn, blendOut, duration, flag, playbackRate)
|
||||
local entity = entityData.spawned
|
||||
local entityId = entityData.id
|
||||
|
||||
if not entity or not DoesEntityExist(entity) or not IsEntityAPed(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
return
|
||||
end
|
||||
|
||||
blendIn = blendIn or 8.0
|
||||
blendOut = blendOut or -8.0
|
||||
duration = duration or -1
|
||||
flag = flag or 0
|
||||
playbackRate = playbackRate or 0.0
|
||||
|
||||
local thread = CreateThread(function()
|
||||
if not HasAnimDictLoaded(animDict) then
|
||||
RequestAnimDict(animDict)
|
||||
local timeout = 100
|
||||
while not HasAnimDictLoaded(animDict) and timeout > 0 do
|
||||
Wait(10)
|
||||
timeout = timeout - 1
|
||||
end
|
||||
end
|
||||
|
||||
if HasAnimDictLoaded(animDict) then
|
||||
TaskPlayAnim(entity, animDict, animName, blendIn, blendOut, duration, flag, playbackRate, false, false, false)
|
||||
|
||||
-- Wait for completion or interruption
|
||||
local startTime = GetGameTimer()
|
||||
local animTime = duration > 0 and (startTime + duration) or -1
|
||||
|
||||
while ClientEntityActions.IsActionRunning[entityId] and entityData.spawned == entity and DoesEntityExist(entity) do
|
||||
local isPlaying = IsEntityPlayingAnim(entity, animDict, animName, 3)
|
||||
|
||||
-- Break conditions:
|
||||
-- 1. Action was stopped/skipped externally
|
||||
-- 2. Entity changed/despawned
|
||||
-- 3. Animation finished naturally (if not looping based on flags/duration)
|
||||
-- 4. Duration expired (if duration > 0)
|
||||
if not ClientEntityActions.IsActionRunning[entityId] or entityData.spawned ~= entity or not DoesEntityExist(entity) then break end
|
||||
if duration == -1 and not isPlaying and GetEntityAnimCurrentTime(entity, animDict, animName) > 0.1 then break end -- Check if non-looping anim finished
|
||||
if animTime ~= -1 and GetGameTimer() >= animTime then break end
|
||||
|
||||
Wait(100) -- Check periodically
|
||||
end
|
||||
-- Don't remove dict here if other actions might use it immediately after
|
||||
-- Consider a separate cleanup mechanism if needed
|
||||
else
|
||||
print(string.format("[ClientEntityActions] Failed to load anim dict '%s' for entity %s", animDict, entityId))
|
||||
end
|
||||
|
||||
-- Crucial: Mark as finished and process next
|
||||
if ClientEntityActions.IsActionRunning[entityId] then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
end)
|
||||
ClientEntityActions.ActionThreads[entityId] = thread
|
||||
end
|
||||
--- Internal implementation for lerping. Registered via RegisterAction.
|
||||
function DefaultActions.LerpTo(entityData, targetCoords, duration, easingType, easingDirection)
|
||||
local entity = entityData.spawned
|
||||
local entityId = entityData.id -- Store ID locally
|
||||
|
||||
if not entity or not DoesEntityExist(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
return
|
||||
end
|
||||
|
||||
local startCoords = GetEntityCoords(entity)
|
||||
local startTime = GetGameTimer()
|
||||
easingType = easingType or "linear"
|
||||
easingDirection = easingDirection or "inout"
|
||||
|
||||
local thread = CreateThread(function()
|
||||
while GetGameTimer() < startTime + duration do
|
||||
-- Check if action should continue
|
||||
if not ClientEntityActions.IsActionRunning[entityId] or not entityData.spawned or entityData.spawned ~= entity or not DoesEntityExist(entity) then
|
||||
break -- Stop if entity despawned, changed, or action was stopped/skipped
|
||||
end
|
||||
|
||||
local elapsed = GetGameTimer() - startTime
|
||||
local t = LA.Clamp(elapsed / duration, 0.0, 1.0)
|
||||
local easedT = LA.EaseInOut(t, easingType) -- Default
|
||||
|
||||
if easingDirection == "in" then
|
||||
easedT = LA.EaseIn(t, easingType)
|
||||
elseif easingDirection == "out" then
|
||||
easedT = LA.EaseOut(t, easingType)
|
||||
end
|
||||
|
||||
local currentPos = LA.LerpVector(startCoords, targetCoords, easedT)
|
||||
SetEntityCoordsNoOffset(entity, currentPos.x, currentPos.y, currentPos.z, false, false, false)
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
-- Ensure final position if completed fully and action wasn't stopped/skipped
|
||||
if ClientEntityActions.IsActionRunning[entityId] and entityData.spawned == entity and DoesEntityExist(entity) then
|
||||
SetEntityCoordsNoOffset(entity, targetCoords.x, targetCoords.y, targetCoords.z, false, false, false)
|
||||
end
|
||||
|
||||
ClientEntityActions.ActionThreads[entityId] = nil -- Clear thread reference
|
||||
-- Only process next action if this thread was the one running the action
|
||||
if ClientEntityActions.IsActionRunning[entityId] then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
end)
|
||||
ClientEntityActions.ActionThreads[entityId] = thread
|
||||
end
|
||||
--- Internal implementation for attaching a prop. Registered via RegisterAction.
|
||||
--- This action completes immediately after attaching. Use DetachProp to remove.
|
||||
--- @param entityData table
|
||||
--- @param propModel string|number
|
||||
--- @param boneIndex number (Optional, default -1 for root)
|
||||
--- @param offsetPos vector3 (Optional, default vector3(0,0,0))
|
||||
--- @param offsetRot vector3 (Optional, default vector3(0,0,0))
|
||||
--- @param useSoftPinning boolean (Optional, default false)
|
||||
--- @param collision boolean (Optional, default false)
|
||||
--- @param isPed boolean (Optional, default false) - Seems unused in native?
|
||||
--- @param vertexIndex number (Optional, default 2) - Seems unused in native?
|
||||
--- @param fixedRot boolean (Optional, default true)
|
||||
function DefaultActions.AttachProp(entityData, propModel, boneName, offsetPos, offsetRot, useSoftPinning, collision, isPed, vertexIndex, fixedRot)
|
||||
local entity = entityData.spawned
|
||||
local entityId = entityData.id
|
||||
|
||||
if not entity or not DoesEntityExist(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
return
|
||||
end
|
||||
|
||||
local modelHash = Utility.GetEntityHashFromModel(propModel)
|
||||
if not Utility.LoadModel(modelHash) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
return
|
||||
end
|
||||
|
||||
local boneIndex = GetEntityBoneIndexByName(entity, boneName)
|
||||
local coords = GetEntityCoords(entity)
|
||||
local prop = CreateObject(modelHash, coords.x, coords.y, coords.z, false, false, false)
|
||||
SetModelAsNoLongerNeeded(modelHash)
|
||||
|
||||
boneIndex = boneIndex or GetPedBoneIndex(entity, 60309) -- SKEL_R_Hand if not specified and is ped
|
||||
if boneIndex == -1 then boneIndex = 0 end -- Default to root if bone not found or not ped
|
||||
offsetPos = offsetPos or vector3(0.0, 0.0, 0.0)
|
||||
offsetRot = offsetRot or vector3(0.0, 0.0, 0.0)
|
||||
AttachEntityToEntity(prop, entity, boneIndex, offsetPos.x, offsetPos.y, offsetPos.z, offsetRot.x, offsetRot.y, offsetRot.z, false, useSoftPinning or false, collision or false, isPed or false, vertexIndex or 2, fixedRot == nil and true or fixedRot)
|
||||
entityData.props = entityData.props or {} -- Ensure props table exists
|
||||
table.insert(entityData.props, prop) -- Store the prop handle in the entity data
|
||||
-- Store the attached prop handle for later removal
|
||||
if not entityData.attachedProps then entityData.attachedProps = {} end
|
||||
entityData.attachedProps[propModel] = prop -- Store by model name/hash for easy lookup
|
||||
|
||||
-- This action finishes immediately
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
--- Internal implementation for detaching a prop. Registered via RegisterAction.
|
||||
--- @param entityData table
|
||||
--- @param propModel string|number The model name/hash of the prop to detach.
|
||||
function DefaultActions.DetachProp(entityData, propModel)
|
||||
local entityId = entityData.id
|
||||
|
||||
if entityData.attachedProps and propModel then
|
||||
local propHandle = entityData.attachedProps[propModel]
|
||||
if propHandle and DoesEntityExist(propHandle) then
|
||||
DetachEntity(propHandle, true, true) -- Detach
|
||||
DeleteEntity(propHandle) -- Delete
|
||||
entityData.attachedProps[propModel] = nil -- Remove from tracking
|
||||
-- print(string.format("[ClientEntityActions] Detached prop '%s' from entity %s", propModel, entityId))
|
||||
else
|
||||
-- print(string.format("[ClientEntityActions] Prop '%s' not found attached to entity %s for detachment.", propModel, entityId))
|
||||
end
|
||||
else
|
||||
-- print(string.format("[ClientEntityActions] No props tracked or propModel not specified for detachment on entity %s.", entityId))
|
||||
end
|
||||
|
||||
-- This action finishes immediately
|
||||
-- ClientEntityActions.IsActionRunning[entityId] = false
|
||||
-- ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
|
||||
function DefaultActions.GetInCar(entityData, vehicleData, seatIndex, timeout)
|
||||
local entity = entityData.spawned
|
||||
local vehicle = vehicleData.spawned
|
||||
local entityId = entityData.id
|
||||
|
||||
if not entity or not DoesEntityExist(entity) or not IsEntityAPed(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
return
|
||||
end
|
||||
|
||||
if not vehicle or not DoesEntityExist(vehicle) or not IsEntityAVehicle(vehicle) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
return
|
||||
end
|
||||
|
||||
-- Clear previous tasks just in case
|
||||
ClearPedTasks(entity)
|
||||
|
||||
local thread = CreateThread(function()
|
||||
TaskEnterVehicle(entity, vehicle, timeout or 1000, seatIndex or -1, 1.0, 1, 0) -- Enter vehicle
|
||||
Wait(timeout or 1000) -- Wait for a bit to ensure the task is completed
|
||||
|
||||
ClientEntityActions.ActionThreads[entityId] = nil -- Clear thread reference
|
||||
-- Only process next action if this thread was the one running the action
|
||||
if ClientEntityActions.IsActionRunning[entityId] then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
end)
|
||||
ClientEntityActions.ActionThreads[entityId] = thread
|
||||
end
|
||||
|
||||
function DefaultActions.Freeze(entityData, freeze)
|
||||
local entity = entityData.spawned
|
||||
local entityId = entityData.id
|
||||
|
||||
if not entity or not DoesEntityExist(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
return
|
||||
end
|
||||
|
||||
FreezeEntityPosition(entity, freeze or true)
|
||||
|
||||
-- This action finishes immediately
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
|
||||
function DefaultActions.PlaceOnGround(entityData)
|
||||
local entity = entityData.spawned
|
||||
local entityId = entityData.id
|
||||
|
||||
if not entity or not DoesEntityExist(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
return
|
||||
end
|
||||
PlaceObjectOnGroundProperly(entity)
|
||||
|
||||
-- This action finishes immediately
|
||||
-- ClientEntityActions.IsActionRunning[entityId] = false
|
||||
-- ClientEntityActions.ProcessNextAction(entityId)
|
||||
end
|
||||
|
||||
function DefaultActions.BobUpAndDown(entityData, speed, height)
|
||||
local entity = entityData.spawned
|
||||
local entityId = entityData.id
|
||||
if not entity or not DoesEntityExist(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
return
|
||||
end
|
||||
CreateThread(function()
|
||||
local coords = GetEntityCoords(entity)
|
||||
local originalZ = coords.z
|
||||
while DoesEntityExist(entity) do
|
||||
-- Calculate the new Z coordinate
|
||||
local newZ = originalZ + math.sin(GetGameTimer() * (speed / 1000)) * height
|
||||
-- Set the new coordinates
|
||||
SetEntityCoords(entity, coords.x, coords.y, newZ)
|
||||
-- Wait for 10 milliseconds
|
||||
Wait(10)
|
||||
end
|
||||
end)
|
||||
-- ClientEntityActions.IsActionRunning[entityId] = false
|
||||
-- ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
end
|
||||
|
||||
function DefaultActions.Circle(entityData, radius, speed)
|
||||
local entity = entityData.spawned
|
||||
local entityId = entityData.id
|
||||
|
||||
if not entity or not DoesEntityExist(entity) then
|
||||
ClientEntityActions.IsActionRunning[entityId] = false
|
||||
ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
return
|
||||
end
|
||||
|
||||
local coords = GetEntityCoords(entity)
|
||||
local angle = 0.0
|
||||
CreateThread(function()
|
||||
while DoesEntityExist(entity) do
|
||||
FreezeEntityPosition(entity, false)
|
||||
local pos = LA.Circle(angle, radius, coords)
|
||||
SetEntityCoords(entity, pos.x, pos.y, pos.z, false, false, false, false)
|
||||
angle = angle + speed * GetFrameTime()
|
||||
FreezeEntityPosition(entity, true)
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
-- ClientEntityActions.IsActionRunning[entityId] = false
|
||||
-- ClientEntityActions.ProcessNextAction(entityId) -- Try next action if this one failed immediately
|
||||
end
|
||||
|
||||
function DefaultActions.Collisions(entityData, enable, keepPhysics)
|
||||
local entity = entityData.spawned
|
||||
SetEntityCollision(entity, enable, keepPhysics)
|
||||
end
|
||||
|
||||
for name, func in pairs(DefaultActions) do
|
||||
ClientEntityActions.RegisterAction(name, func)
|
||||
end
|
||||
|
||||
|
||||
return ClientEntityActions
|
Loading…
Add table
Add a link
Reference in a new issue