├── README.md ├── client.lua ├── fxmanifest.lua └── html ├── index.html ├── script.js └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # Progressbar 2 | 3 | Dependency for creating progressbars in QB-Core. 4 | 5 | # Usage 6 | 7 | ## QB-Core Functions 8 | 9 | ### Client 10 | 11 | - QBCore.Functions.Progressbar(**name**: string, **label**: string, **duration**: number, **useWhileDead**: boolean, **canCancel**: boolean, **disableControls**: table, **animation**: table, **prop**: table, **propTwo**: table, **onFinish**: function, **onCancel**: function) 12 | > Create a new progressbar from the built in qb-core functions.
13 | > **Example:** 14 | > ```lua 15 | >QBCore.Functions.Progressbar("random_task", "Doing something", 5000, false, true, { 16 | > disableMovement = false, 17 | > disableCarMovement = false, 18 | > disableMouse = false, 19 | > disableCombat = true, 20 | >}, { 21 | > animDict = "mp_suicide", 22 | > anim = "pill", 23 | > flags = 49, 24 | >}, {}, {}, function() 25 | > -- Done 26 | >end, function() 27 | > -- Cancel 28 | >end) 29 | > ``` 30 | 31 | ## Exports 32 | 33 | ### Client 34 | 35 | - Progress(**data**: string, **handler**: function) 36 | > Creates a new progress bar directly from the export, always use the built in qb-core function if possible.
37 | > **Example:** 38 | > ```lua 39 | >exports['progressbar']:Progress({ 40 | > name = "random_task", 41 | > duration = 5000, 42 | > label = "Doing something", 43 | > useWhileDead = false, 44 | > canCancel = true, 45 | > controlDisables = { 46 | > disableMovement = false, 47 | > disableCarMovement = false, 48 | > disableMouse = false, 49 | > disableCombat = true, 50 | > }, 51 | > animation = { 52 | > animDict = "mp_suicide", 53 | > anim = "pill", 54 | > flags = 49, 55 | > }, 56 | > prop = {}, 57 | > propTwo = {} 58 | >}, function(cancelled) 59 | > if not cancelled then 60 | > -- finished 61 | > else 62 | > -- cancelled 63 | > end 64 | >end) 65 | > ``` 66 | > **Props Example:** 67 | > ```lua 68 | >exports['progressbar']:Progress({ 69 | > name = "random_task", 70 | > duration = 5000, 71 | > label = "Doing something", 72 | > useWhileDead = false, 73 | > canCancel = true, 74 | > controlDisables = { 75 | > disableMovement = false, 76 | > disableCarMovement = false, 77 | > disableMouse = false, 78 | > disableCombat = true, 79 | > }, 80 | > animation = { 81 | > animDict = "missheistdockssetup1clipboard@base", 82 | > anim = "pill", 83 | > flags = 49, 84 | > }, 85 | > prop = { 86 | > model = 'prop_notepad_01', 87 | > bone = 18905, 88 | > coords = vec3(0.1, 0.02, 0.05), 89 | > rotation = vec3(10.0, 0.0, 0.0), 90 | > }, 91 | > propTwo = { 92 | > model = 'prop_pencil_01', 93 | > bone = 58866, 94 | > coords = vec3(0.11, -0.02, 0.001), 95 | > rotation = vec3(-120.0, 0.0, 0.0), 96 | > } 97 | >}, function(cancelled) 98 | > if not cancelled then 99 | > -- finished 100 | > else 101 | > -- cancelled 102 | > end 103 | >end) 104 | > ``` 105 | 106 | - isDoingSomething() 107 | > Returns a boolean (true/false) depending on if a progressbar is present.
108 | > **Example:** 109 | > ```lua 110 | > local busy = exports["progressbar"]:isDoingSomething() 111 | > ``` 112 | 113 | - ProgressWithStartEvent(**data**: table, **start**: function, **finish**: function) 114 | > Works like a normal progressbar, the data parameter should be the same as the data passed into the `Progress` export above.
115 | > The start function gets triggered upon the start of the progressbar.
116 | > The finish handler is the same as the `handler` parameter in the `Progress` export above. 117 | 118 | - ProgressWithTickEvent(**data**: table, **tick**: function, **finish**: function) 119 | > Works like a normal progressbar, the data parameter should be the same as the data passed into the `Progress` export above.
120 | > The tick function gets triggered every frame while the progressbar is active.
121 | > The finish handler is the same as the `handler` parameter in the `Progress` export above. 122 | 123 | - ProgressWithTickEvent(**data**: table, **start**: function, **tick**: function, **finish**: function) 124 | > Works like a normal progressbar, the data parameter should be the same as the data passed into the `Progress` export above.
125 | > The start function gets triggered upon the start of the progressbar.
126 | > The tick function gets triggered every frame while the progressbar is active.
127 | > The finish handler is the same as the `handler` parameter in the `Progress` export above. 128 | -------------------------------------------------------------------------------- /client.lua: -------------------------------------------------------------------------------- 1 | local Action = { 2 | name = '', 3 | duration = 0, 4 | label = '', 5 | useWhileDead = false, 6 | canCancel = true, 7 | disarm = true, 8 | controlDisables = { 9 | disableMovement = false, 10 | disableCarMovement = false, 11 | disableMouse = false, 12 | disableCombat = false, 13 | }, 14 | animation = { 15 | animDict = nil, 16 | anim = nil, 17 | flags = 0, 18 | task = nil, 19 | }, 20 | prop = { 21 | model = nil, 22 | bone = nil, 23 | coords = vec3(0.0, 0.0, 0.0), 24 | rotation = vec3(0.0, 0.0, 0.0), 25 | }, 26 | propTwo = { 27 | model = nil, 28 | bone = nil, 29 | coords = vec3(0.0, 0.0, 0.0), 30 | rotation = vec3(0.0, 0.0, 0.0), 31 | }, 32 | } 33 | 34 | local isDoingAction = false 35 | local wasCancelled = false 36 | local prop_net = nil 37 | local propTwo_net = nil 38 | local isAnim = false 39 | local isProp = false 40 | local isPropTwo = false 41 | 42 | local controls = { 43 | disableMouse = { 1, 2, 106 }, 44 | disableMovement = { 30, 31, 36, 21, 75 }, 45 | disableCarMovement = { 63, 64, 71, 72 }, 46 | disableCombat = { 24, 25, 37, 47, 58, 140, 141, 142, 143, 263, 264, 257 } 47 | } 48 | 49 | -- Functions 50 | 51 | local function loadAnimDict(dict) 52 | RequestAnimDict(dict) 53 | while not HasAnimDictLoaded(dict) do 54 | Wait(5) 55 | end 56 | end 57 | 58 | local function loadModel(model) 59 | RequestModel(model) 60 | while not HasModelLoaded(model) do 61 | Wait(5) 62 | end 63 | end 64 | 65 | local function createAndAttachProp(prop, ped) 66 | loadModel(prop.model) 67 | local coords = GetOffsetFromEntityInWorldCoords(ped, 0.0, 0.0, 0.0) 68 | local propEntity = CreateObject(GetHashKey(prop.model), coords.x, coords.y, coords.z, true, true, true) 69 | local netId = ObjToNet(propEntity) 70 | SetNetworkIdExistsOnAllMachines(netId, true) 71 | NetworkUseHighPrecisionBlending(netId, true) 72 | SetNetworkIdCanMigrate(netId, false) 73 | local boneIndex = GetPedBoneIndex(ped, prop.bone or 60309) 74 | AttachEntityToEntity( 75 | propEntity, ped, boneIndex, 76 | prop.coords.x, prop.coords.y, prop.coords.z, 77 | prop.rotation.x, prop.rotation.y, prop.rotation.z, 78 | true, true, false, true, 0, true 79 | ) 80 | return netId 81 | end 82 | 83 | local function disableControls() 84 | CreateThread(function() 85 | while isDoingAction do 86 | for disableType, isEnabled in pairs(Action.controlDisables) do 87 | if isEnabled and controls[disableType] then 88 | for _, control in ipairs(controls[disableType]) do 89 | DisableControlAction(0, control, true) 90 | end 91 | end 92 | end 93 | if Action.controlDisables.disableCombat then 94 | DisablePlayerFiring(PlayerId(), true) 95 | end 96 | Wait(0) 97 | end 98 | end) 99 | end 100 | 101 | local function StartActions() 102 | local ped = PlayerPedId() 103 | if isDoingAction then 104 | if not isAnim and Action.animation then 105 | if Action.animation.task then 106 | TaskStartScenarioInPlace(ped, Action.animation.task, 0, true) 107 | else 108 | local anim = Action.animation 109 | if anim.animDict and anim.anim and DoesEntityExist(ped) and not IsEntityDead(ped) then 110 | loadAnimDict(anim.animDict) 111 | TaskPlayAnim(ped, anim.animDict, anim.anim, 3.0, 3.0, -1, anim.flags or 1, 0, false, false, false) 112 | end 113 | end 114 | isAnim = true 115 | end 116 | if not isProp and Action.prop and Action.prop.model then 117 | prop_net = createAndAttachProp(Action.prop, ped) 118 | isProp = true 119 | end 120 | if not isPropTwo and Action.propTwo and Action.propTwo.model then 121 | propTwo_net = createAndAttachProp(Action.propTwo, ped) 122 | isPropTwo = true 123 | end 124 | disableControls() 125 | end 126 | end 127 | 128 | local function StartProgress(action, onStart, onTick, onFinish) 129 | local playerPed = PlayerPedId() 130 | local isPlayerDead = IsEntityDead(playerPed) 131 | if (not isPlayerDead or action.useWhileDead) and not isDoingAction then 132 | isDoingAction = true 133 | LocalPlayer.state:set('inv_busy', true, true) 134 | Action = action 135 | SendNUIMessage({ 136 | action = 'progress', 137 | duration = action.duration, 138 | label = action.label 139 | }) 140 | StartActions() 141 | CreateThread(function() 142 | if onStart then onStart() end 143 | while isDoingAction do 144 | Wait(1) 145 | if onTick then onTick() end 146 | if IsControlJustPressed(0, 200) and action.canCancel then 147 | TriggerEvent('progressbar:client:cancel') 148 | wasCancelled = true 149 | break 150 | end 151 | if IsEntityDead(playerPed) and not action.useWhileDead then 152 | TriggerEvent('progressbar:client:cancel') 153 | wasCancelled = true 154 | break 155 | end 156 | end 157 | if onFinish then onFinish(wasCancelled) end 158 | isDoingAction = false 159 | end) 160 | end 161 | end 162 | 163 | local function ActionCleanup() 164 | local ped = PlayerPedId() 165 | if Action.animation then 166 | if Action.animation.task or (Action.animation.animDict and Action.animation.anim) then 167 | StopAnimTask(ped, Action.animation.animDict, Action.animation.anim, 1.0) 168 | ClearPedSecondaryTask(ped) 169 | else 170 | ClearPedTasks(ped) 171 | end 172 | end 173 | if prop_net then 174 | DetachEntity(NetToObj(prop_net), true, true) 175 | DeleteObject(NetToObj(prop_net)) 176 | end 177 | if propTwo_net then 178 | DetachEntity(NetToObj(propTwo_net), true, true) 179 | DeleteObject(NetToObj(propTwo_net)) 180 | end 181 | prop_net = nil 182 | propTwo_net = nil 183 | isDoingAction = false 184 | wasCancelled = false 185 | isAnim = false 186 | isProp = false 187 | isPropTwo = false 188 | LocalPlayer.state:set('inv_busy', false, true) 189 | end 190 | 191 | -- Events 192 | 193 | RegisterNetEvent('progressbar:client:ToggleBusyness', function(bool) 194 | isDoingAction = bool 195 | end) 196 | 197 | RegisterNetEvent('progressbar:client:progress', function(action, finish) 198 | StartProgress(action, nil, nil, finish) 199 | end) 200 | 201 | RegisterNetEvent('progressbar:client:ProgressWithStartEvent', function(action, start, finish) 202 | StartProgress(action, start, nil, finish) 203 | end) 204 | 205 | RegisterNetEvent('progressbar:client:ProgressWithTickEvent', function(action, tick, finish) 206 | StartProgress(action, nil, tick, finish) 207 | end) 208 | 209 | RegisterNetEvent('progressbar:client:ProgressWithStartAndTick', function(action, start, tick, finish) 210 | StartProgress(action, start, tick, finish) 211 | end) 212 | 213 | RegisterNetEvent('progressbar:client:cancel', function() 214 | ActionCleanup() 215 | SendNUIMessage({ 216 | action = 'cancel' 217 | }) 218 | end) 219 | 220 | -- NUI Callback 221 | 222 | RegisterNUICallback('FinishAction', function(data, cb) 223 | ActionCleanup() 224 | cb('ok') 225 | end) 226 | 227 | -- Exports 228 | 229 | local function Progress(action, finish) 230 | StartProgress(action, nil, nil, finish) 231 | end 232 | exports('Progress', Progress) 233 | 234 | local function ProgressWithStartEvent(action, start, finish) 235 | StartProgress(action, start, nil, finish) 236 | end 237 | exports('ProgressWithStartEvent', ProgressWithStartEvent) 238 | 239 | local function ProgressWithTickEvent(action, tick, finish) 240 | StartProgress(action, nil, tick, finish) 241 | end 242 | exports('ProgressWithTickEvent', ProgressWithTickEvent) 243 | 244 | local function ProgressWithStartAndTick(action, start, tick, finish) 245 | StartProgress(action, start, tick, finish) 246 | end 247 | exports('ProgressWithStartAndTick', ProgressWithStartAndTick) 248 | 249 | local function isDoingSomething() 250 | return isDoingAction 251 | end 252 | exports('isDoingSomething', isDoingSomething) 253 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | lua54 'yes' 3 | game 'gta5' 4 | 5 | author 'qbcore-framework' 6 | description 'Dependency for creating progressbars in QB-Core.' 7 | version '1.0.0' 8 | 9 | ui_page 'html/index.html' 10 | 11 | client_script 'client.lua' 12 | 13 | files { 14 | 'html/index.html', 15 | 'html/style.css', 16 | 'html/script.js' 17 | } 18 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 |
Loading...
9 |
0%
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /html/script.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", (event) => { 2 | var ProgressBar = { 3 | init: function () { 4 | this.progressLabel = document.getElementById("progress-label"); 5 | this.progressPercentage = document.getElementById("progress-percentage"); 6 | this.progressBar = document.getElementById("progress-bar"); 7 | this.progressContainer = document.querySelector(".progress-container"); 8 | this.animationFrameRequest = null; 9 | this.setupListeners(); 10 | }, 11 | 12 | setupListeners: function () { 13 | window.addEventListener("message", function (event) { 14 | if (event.data.action === "progress") { 15 | ProgressBar.update(event.data); 16 | } else if (event.data.action === "cancel") { 17 | ProgressBar.cancel(); 18 | } 19 | }); 20 | }, 21 | 22 | update: function (data) { 23 | if (this.animationFrameRequest) { 24 | cancelAnimationFrame(this.animationFrameRequest); 25 | } 26 | clearTimeout(this.cancelledTimer); 27 | 28 | this.progressLabel.textContent = data.label; 29 | this.progressPercentage.textContent = "0%"; 30 | this.progressContainer.style.display = "block"; 31 | let startTime = Date.now(); 32 | let duration = parseInt(data.duration, 10); 33 | 34 | const animateProgress = () => { 35 | let timeElapsed = Date.now() - startTime; 36 | let progress = timeElapsed / duration; 37 | if (progress > 1) progress = 1; 38 | let percentage = Math.round(progress * 100); 39 | this.progressBar.style.width = percentage + "%"; 40 | this.progressPercentage.textContent = percentage + "%"; 41 | if (progress < 1) { 42 | this.animationFrameRequest = requestAnimationFrame(animateProgress); 43 | } else { 44 | this.onComplete(); 45 | } 46 | }; 47 | this.animationFrameRequest = requestAnimationFrame(animateProgress); 48 | }, 49 | 50 | cancel: function () { 51 | if (this.animationFrameRequest) { 52 | cancelAnimationFrame(this.animationFrameRequest); 53 | this.animationFrameRequest = null; 54 | } 55 | this.progressLabel.textContent = "CANCELLED"; 56 | this.progressPercentage.textContent = ""; 57 | this.progressBar.style.width = "100%"; 58 | this.cancelledTimer = setTimeout(this.onCancel.bind(this), 1000); 59 | }, 60 | 61 | onComplete: function () { 62 | this.progressContainer.style.display = "none"; 63 | this.progressBar.style.width = "0"; 64 | this.progressPercentage.textContent = ""; 65 | this.postAction("FinishAction"); 66 | }, 67 | 68 | onCancel: function () { 69 | this.progressContainer.style.display = "none"; 70 | this.progressBar.style.width = "0"; 71 | this.progressPercentage.textContent = ""; 72 | }, 73 | 74 | postAction: function (action) { 75 | fetch(`https://progressbar/${action}`, { 76 | method: "POST", 77 | headers: { 78 | "Content-Type": "application/json", 79 | }, 80 | body: JSON.stringify({}), 81 | }); 82 | }, 83 | 84 | closeUI: function () { 85 | let mainContainer = document.querySelector(".main-container"); 86 | if (mainContainer) { 87 | mainContainer.style.display = "none"; 88 | } 89 | }, 90 | }; 91 | 92 | ProgressBar.init(); 93 | }); 94 | -------------------------------------------------------------------------------- /html/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200&display=swap"); 2 | 3 | html { 4 | overflow: hidden; 5 | font-family: "Poppins", sans-serif; 6 | } 7 | 8 | body { 9 | background: transparent !important; 10 | margin: 0; 11 | padding: 0; 12 | overflow: hidden; 13 | height: 100%; 14 | width: 100%; 15 | } 16 | 17 | .progress-container { 18 | display: none; 19 | z-index: 5; 20 | color: #fff; 21 | width: 15%; 22 | position: fixed; 23 | bottom: 15%; 24 | left: 0; 25 | right: 0; 26 | margin-left: auto; 27 | margin-right: auto; 28 | font-family: "Poppins", sans-serif; 29 | } 30 | 31 | .progress-labels { 32 | display: flex; 33 | justify-content: space-between; 34 | align-items: center; 35 | } 36 | 37 | #progress-label { 38 | font-size: 1.3vh; 39 | line-height: 4vh; 40 | position: relative; 41 | color: #ffffff; 42 | z-index: 10; 43 | font-weight: bold; 44 | } 45 | 46 | #progress-percentage { 47 | font-size: 1.3vh; 48 | line-height: 4vh; 49 | position: relative; 50 | color: #ffffff; 51 | z-index: 10; 52 | font-weight: bold; 53 | } 54 | 55 | .progress-bar-container { 56 | background: rgba(0, 0, 0, 0.246); 57 | height: 0.5vh; 58 | position: relative; 59 | display: block; 60 | border-radius: 2px; 61 | } 62 | 63 | #progress-bar { 64 | background-color: #dc143c; 65 | width: 0%; 66 | height: 0.5vh; 67 | border-radius: 2px; 68 | transition: width 0.3s; 69 | transition-timing-function: ease-out; 70 | box-shadow: 0 0 10px rgba(220, 20, 60, 0.6); 71 | } 72 | --------------------------------------------------------------------------------