local progressRes local isStopping = false FM.progress = {} ---@class Anim ---@field anim string ---@field dict string ---@field position? vector3 ---@field rotation? vector3 ---@field enterSpeed? number ---@field exitSpeed? number ---@field duration? number ---@field flag? number ---@field animTime? number ---@class Prop ---@field model string | number ---@field position vector3 ---@field rotation vector3 ---@field bone? number ---@class fmProgressProps ---@field label? string ---@field time? number ---@field type? 'linear' | 'circle' ---@field failedLabel? string ---@field completedLabel? string ---@field canCancel? boolean ---@field anim? Anim ---@field prop? Prop ---@field scenario? { name: string, position?: vector3, heading?: number, duration?: number, sitting?: boolean, teleport?: boolean } ---@field disable? { move?: boolean, car?: boolean, combat?: boolean, mouse?: boolean } -- Credits to ox_lib ---@field useSfx? boolean ---@type fmProgressProps | nil local currProps -- CREDITS TO ox_lib FOR THE CONTROLS local controls = { INPUT_LOOK_LR = 1, INPUT_LOOK_UD = 2, INPUT_SPRINT = 21, INPUT_AIM = 25, INPUT_MOVE_LR = 30, INPUT_MOVE_UD = 31, INPUT_DUCK = 36, INPUT_VEH_MOVE_LEFT_ONLY = 63, INPUT_VEH_MOVE_RIGHT_ONLY = 64, INPUT_VEH_ACCELERATE = 71, INPUT_VEH_BRAKE = 72, INPUT_VEH_EXIT = 75, INPUT_VEH_MOUSE_CONTROL_OVERRIDE = 106 } local function initProgressThread() while progressRes ~= nil do if currProps.disable then if currProps.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 currProps.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 currProps.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 currProps.disable.combat then DisableControlAction(0, controls.INPUT_AIM, true) DisablePlayerFiring(PlayerId(), true) end end Wait(0) end end local function stopCurrentAnim() if currProps.anim and currProps.anim.anim and currProps.anim.dict then if IsEntityPlayingAnim(PlayerPedId(), currProps.anim.dict, currProps.anim.anim, 3) then ClearPedTasks(PlayerPedId()) end RemoveAnimDict(currProps.anim.dict) elseif currProps.scenario and IsPedUsingScenario(PlayerPedId(), currProps.scenario.name) then ClearPedTasks(PlayerPedId()) end end local function createProp(prop) if type(prop.position) ~= 'vector3' and type(prop.position) ~= 'table' then FM.console.err('Invalid position') return end if type(prop.rotation) ~= 'vector3' and type(prop.rotation) ~= 'table' then FM.console.err('Invalid rotation') return end if not tonumber(prop.model) then prop.model = joaat(prop.model) end if not IsModelValid(prop.model) then FM.console.err('Invalid model: '..prop.model) return end if not HasModelLoaded(prop.model) then RequestModel(prop.model) while not HasModelLoaded(prop.model) do Wait(200) end end local ped = PlayerPedId() local pCoords = GetEntityCoords(ped) local obj = CreateObject(prop.model, pCoords, true, true, true) AttachEntityToEntity(obj, ped, GetPedBoneIndex(ped, prop.bone or 60309), prop.position, prop.rotation, true, true, false, true, 0, true) SetModelAsNoLongerNeeded(prop.model) return obj end local function setDefaultProps(props) if not props then props = {} end props.label = props.label or 'Progress' props.time = props.time or 3000 props.type = props.type or 'linear' props.completedLabel = props.completedLabel or 'Completed' props.failedLabel = props.failedLabel or 'Failed' if props.canCancel == nil then props.canCancel = true end if props.useSfx == nil then props.useSfx = true end if not props.anim and props.scenario then props.scenario.position = props.scenario.position or GetEntityCoords(PlayerPedId()) props.scenario.heading = props.scenario.heading or GetEntityHeading(PlayerPedId()) props.scenario.duration = props.scenario.duration or -1 if props.scenario.sitting == nil then props.scenario.sitting = false end if props.scenario.teleport == nil then props.scenario.teleport = false end end if not props.disable then props.disable = {} end return props end ---@param props fmProgressProps | nil function FM.progress.start(props) if progressRes ~= nil or isStopping then return FM.console.err('Progress already active') end currProps = setDefaultProps(props) local override, res = OverrideProgress(currProps) if override then return res end progressRes = promise.new() isStopping = false SendNUIMessage({ action = 'startProgress', data = currProps }) if currProps.anim then FM.anim.play(currProps.anim) elseif currProps.scenario then TaskStartScenarioAtPosition(PlayerPedId(), currProps.scenario.name, currProps.scenario.position, currProps.scenario.heading, currProps.scenario.duration, currProps.scenario.sitting, currProps.scenario.teleport) end if currProps.prop then currProps.prop = createProp(currProps.prop) end CreateThread(initProgressThread) return Citizen.Await(progressRes) end ---@param success boolean function FM.progress.stop(success) if StopOverrideProgress() then return end if progressRes == nil then return FM.console.err('No progress active') end if isStopping then return FM.console.err('Progress already stopping') end isStopping = true SendNUIMessage({ action = 'stopProgress', data = success }) end RegisterNUICallback('progressStopped', function(success, cb) isStopping = true if progressRes then progressRes:resolve(success) progressRes = nil if currProps.anim or currProps.scenario then stopCurrentAnim() end if currProps.prop then DeleteEntity(currProps.prop) end currProps = nil end isStopping = false cb(true) end) ---@return boolean function FM.progress.isActive() return progressRes ~= nil end RegisterCommand('progresscancel', function(source, args, raw) if isStopping or progressRes == nil or not currProps or not currProps.canCancel then return end FM.progress.stop(false) end) RegisterKeyMapping('progresscancel', 'Cancel Progress', KeyMappings.CANCEL.mapper, KeyMappings.CANCEL.key) --[[ EXAMPLE FOR NOW HERE ]] -- RegisterCommand('startprogress', function(source, args, raw) -- if FM.progress.start({ -- label = 'Testing progress', -- time = 10000, -- canCancel = true, -- type = 'linear', -- failedLabel = 'Progress Failed!', -- completedLabel = 'Progress Completed!', -- anim = { dict = "amb@world_human_gardener_plant@male@base", anim = "base" }, -- prop = { model = 'prop_tool_broom', position = vector3(0.0, 0.0, 0.0), rotation = vector3(0.0, 0.0, 0.0), bone = 60309 }, -- }) then -- FM.console.debug('Progress success') -- else -- FM.console.debug('Progress failed') -- end -- end)