269 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			269 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --[[
 | |
|     https://github.com/overextended/ox_lib
 | |
| 
 | |
|     This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
 | |
| 
 | |
|     Copyright © 2025 Linden <https://github.com/thelindat>
 | |
| ]]
 | |
| 
 | |
| local progress
 | |
| local DisableControlAction = DisableControlAction
 | |
| local DisablePlayerFiring = DisablePlayerFiring
 | |
| local playerState = LocalPlayer.state
 | |
| local createdProps = {}
 | |
| 
 | |
| ---@class ProgressPropProps
 | |
| ---@field model string
 | |
| ---@field bone? number
 | |
| ---@field pos vector3
 | |
| ---@field rot vector3
 | |
| ---@field rotOrder? number
 | |
| 
 | |
| ---@class ProgressProps
 | |
| ---@field label? string
 | |
| ---@field duration number
 | |
| ---@field position? 'middle' | 'bottom'
 | |
| ---@field useWhileDead? boolean
 | |
| ---@field allowRagdoll? boolean
 | |
| ---@field allowCuffed? boolean
 | |
| ---@field allowFalling? boolean
 | |
| ---@field allowSwimming? boolean
 | |
| ---@field canCancel? boolean
 | |
| ---@field anim? { dict?: string, clip: string, flag?: number, blendIn?: number, blendOut?: number, duration?: number, playbackRate?: number, lockX?: boolean, lockY?: boolean, lockZ?: boolean, scenario?: string, playEnter?: boolean }
 | |
| ---@field prop? ProgressPropProps | ProgressPropProps[]
 | |
| ---@field disable? { move?: boolean, sprint?: boolean, car?: boolean, combat?: boolean, mouse?: boolean }
 | |
| 
 | |
| local function createProp(ped, prop)
 | |
|     lib.requestModel(prop.model)
 | |
|     local coords = GetEntityCoords(ped)
 | |
|     local object = CreateObject(prop.model, coords.x, coords.y, coords.z, false, false, false)
 | |
| 
 | |
|     AttachEntityToEntity(object, ped, GetPedBoneIndex(ped, prop.bone or 60309), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true,
 | |
|         true, false, true, prop.rotOrder or 0, true)
 | |
|     SetModelAsNoLongerNeeded(prop.model)
 | |
| 
 | |
|     return object
 | |
| end
 | |
| 
 | |
| local function interruptProgress(data)
 | |
|     if not data.useWhileDead and IsEntityDead(cache.ped) then return true end
 | |
|     if not data.allowRagdoll and IsPedRagdoll(cache.ped) then return true end
 | |
|     if not data.allowCuffed and IsPedCuffed(cache.ped) then return true end
 | |
|     if not data.allowFalling and IsPedFalling(cache.ped) then return true end
 | |
|     if not data.allowSwimming and IsPedSwimming(cache.ped) then return true end
 | |
| end
 | |
| 
 | |
| local isFivem = cache.game == 'fivem'
 | |
| 
 | |
| local controls = {
 | |
|     INPUT_LOOK_LR = isFivem and 1 or 0xA987235F,
 | |
|     INPUT_LOOK_UD = isFivem and 2 or 0xD2047988,
 | |
|     INPUT_SPRINT = isFivem and 21 or 0x8FFC75D6,
 | |
|     INPUT_AIM = isFivem and 25 or 0xF84FA74F,
 | |
|     INPUT_MOVE_LR = isFivem and 30 or 0x4D8FB4C1,
 | |
|     INPUT_MOVE_UD = isFivem and 31 or 0xFDA83190,
 | |
|     INPUT_DUCK = isFivem and 36 or 0xDB096B85,
 | |
|     INPUT_VEH_MOVE_LEFT_ONLY = isFivem and 63 or 0x9DF54706,
 | |
|     INPUT_VEH_MOVE_RIGHT_ONLY = isFivem and 64 or 0x97A8FD98,
 | |
|     INPUT_VEH_ACCELERATE = isFivem and 71 or 0x5B9FD4E2,
 | |
|     INPUT_VEH_BRAKE = isFivem and 72 or 0x6E1F639B,
 | |
|     INPUT_VEH_EXIT = isFivem and 75 or 0xFEFAB9B4,
 | |
|     INPUT_VEH_MOUSE_CONTROL_OVERRIDE = isFivem and 106 or 0x39CCABD5
 | |
| }
 | |
| 
 | |
| ---@param data ProgressProps
 | |
| local function startProgress(data)
 | |
|     playerState.invBusy = true
 | |
|     progress = data
 | |
|     local anim = data.anim
 | |
| 
 | |
|     if anim then
 | |
|         if anim.dict then
 | |
|             lib.requestAnimDict(anim.dict)
 | |
| 
 | |
|             TaskPlayAnim(cache.ped, anim.dict, anim.clip, anim.blendIn or 3.0, anim.blendOut or 1.0, anim.duration or -1, anim.flag or 49, anim.playbackRate or 0,
 | |
|                 anim.lockX, anim.lockY, anim.lockZ)
 | |
|             RemoveAnimDict(anim.dict)
 | |
|         elseif anim.scenario then
 | |
|             TaskStartScenarioInPlace(cache.ped, anim.scenario, 0, anim.playEnter == nil or anim.playEnter --[[@as boolean]])
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     if data.prop then
 | |
|         playerState:set('lib:progressProps', data.prop, true)
 | |
|     end
 | |
| 
 | |
|     local disable = data.disable
 | |
|     local startTime = GetGameTimer()
 | |
| 
 | |
|     while progress do
 | |
|         if disable then
 | |
|             if disable.mouse then
 | |
|                 DisableControlAction(0, controls.INPUT_LOOK_LR, true)
 | |
|                 DisableControlAction(0, controls.INPUT_LOOK_UD, true)
 | |
|                 DisableControlAction(0, controls.INPUT_VEH_MOUSE_CONTROL_OVERRIDE, true)
 | |
|             end
 | |
| 
 | |
|             if disable.move then
 | |
|                 DisableControlAction(0, controls.INPUT_SPRINT, true)
 | |
|                 DisableControlAction(0, controls.INPUT_MOVE_LR, true)
 | |
|                 DisableControlAction(0, controls.INPUT_MOVE_UD, true)
 | |
|                 DisableControlAction(0, controls.INPUT_DUCK, true)
 | |
|             end
 | |
| 
 | |
|             if disable.sprint and not disable.move then
 | |
|                 DisableControlAction(0, controls.INPUT_SPRINT, true)
 | |
|             end
 | |
| 
 | |
|             if disable.car then
 | |
|                 DisableControlAction(0, controls.INPUT_VEH_MOVE_LEFT_ONLY, true)
 | |
|                 DisableControlAction(0, controls.INPUT_VEH_MOVE_RIGHT_ONLY, true)
 | |
|                 DisableControlAction(0, controls.INPUT_VEH_ACCELERATE, true)
 | |
|                 DisableControlAction(0, controls.INPUT_VEH_BRAKE, true)
 | |
|                 DisableControlAction(0, controls.INPUT_VEH_EXIT, true)
 | |
|             end
 | |
| 
 | |
|             if disable.combat then
 | |
|                 DisableControlAction(0, controls.INPUT_AIM, true)
 | |
|                 DisablePlayerFiring(cache.playerId, true)
 | |
|             end
 | |
|         end
 | |
| 
 | |
|         if interruptProgress(progress) then
 | |
|             progress = false
 | |
|         end
 | |
| 
 | |
|         Wait(0)
 | |
|     end
 | |
| 
 | |
|     if data.prop then
 | |
|         playerState:set('lib:progressProps', nil, true)
 | |
|     end
 | |
| 
 | |
|     if anim then
 | |
|         if anim.dict then
 | |
|             StopAnimTask(cache.ped, anim.dict, anim.clip, 1.0)
 | |
|             Wait(0) -- This is needed here otherwise the StopAnimTask is cancelled
 | |
|         else
 | |
|             ClearPedTasks(cache.ped)
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     playerState.invBusy = false
 | |
|     local duration = progress ~= false and GetGameTimer() - startTime + 100 -- give slight leeway
 | |
| 
 | |
|     if progress == false or duration <= data.duration then
 | |
|         SendNUIMessage({ action = 'progressCancel' })
 | |
|         return false
 | |
|     end
 | |
| 
 | |
|     return true
 | |
| end
 | |
| 
 | |
| ---@param data ProgressProps
 | |
| ---@return boolean?
 | |
| function lib.progressBar(data)
 | |
|     while progress ~= nil do Wait(0) end
 | |
| 
 | |
|     if not interruptProgress(data) then
 | |
|         SendNUIMessage({
 | |
|             action = 'progress',
 | |
|             data = {
 | |
|                 label = data.label,
 | |
|                 duration = data.duration
 | |
|             }
 | |
|         })
 | |
| 
 | |
|         return startProgress(data)
 | |
|     end
 | |
| end
 | |
| 
 | |
| ---@param data ProgressProps
 | |
| ---@return boolean?
 | |
| function lib.progressCircle(data)
 | |
|     while progress ~= nil do Wait(0) end
 | |
| 
 | |
|     if not interruptProgress(data) then
 | |
|         SendNUIMessage({
 | |
|             action = 'circleProgress',
 | |
|             data = {
 | |
|                 duration = data.duration,
 | |
|                 position = data.position,
 | |
|                 label = data.label
 | |
|             }
 | |
|         })
 | |
| 
 | |
|         return startProgress(data)
 | |
|     end
 | |
| end
 | |
| 
 | |
| function lib.cancelProgress()
 | |
|     if not progress then
 | |
|         error('No progress bar is active')
 | |
|     end
 | |
| 
 | |
|     progress = false
 | |
| end
 | |
| 
 | |
| ---@return boolean
 | |
| function lib.progressActive()
 | |
|     return progress and true
 | |
| end
 | |
| 
 | |
| RegisterNUICallback('progressComplete', function(data, cb)
 | |
|     cb(1)
 | |
|     progress = nil
 | |
| end)
 | |
| 
 | |
| RegisterCommand('cancelprogress', function()
 | |
|     if progress?.canCancel then progress = false end
 | |
| end)
 | |
| 
 | |
| if isFivem then
 | |
|     RegisterKeyMapping('cancelprogress', locale('cancel_progress'), 'keyboard', 'x')
 | |
| end
 | |
| 
 | |
| local function deleteProgressProps(serverId)
 | |
|     local playerProps = createdProps[serverId]
 | |
|     if not playerProps then return end
 | |
|     for i = 1, #playerProps do
 | |
|         local prop = playerProps[i]
 | |
|         if DoesEntityExist(prop) then
 | |
|             DeleteEntity(prop)
 | |
|         end
 | |
|     end
 | |
|     createdProps[serverId] = nil
 | |
| end
 | |
| 
 | |
| RegisterNetEvent('onPlayerDropped', function(serverId)
 | |
|     deleteProgressProps(serverId)
 | |
| end)
 | |
| 
 | |
| AddStateBagChangeHandler('lib:progressProps', nil, function(bagName, key, value, reserved, replicated)
 | |
|     if replicated then return end
 | |
| 
 | |
|     local ply = GetPlayerFromStateBagName(bagName)
 | |
|     if ply == 0 then return end
 | |
| 
 | |
|     local ped = GetPlayerPed(ply)
 | |
|     local serverId = GetPlayerServerId(ply)
 | |
| 
 | |
|     if not value then
 | |
|         return deleteProgressProps(serverId)
 | |
|     end
 | |
| 
 | |
|     createdProps[serverId] = {}
 | |
|     local playerProps = createdProps[serverId]
 | |
| 
 | |
|     if value.model then
 | |
|         playerProps[#playerProps + 1] = createProp(ped, value)
 | |
|     else
 | |
|         for i = 1, #value do
 | |
|             local prop = value[i]
 | |
| 
 | |
|             if prop then
 | |
|                 playerProps[#playerProps + 1] = createProp(ped, prop)
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end)
 | 
