Overview
Access viaProgress (client-side only).
Visual Feedback
On-screen progress bar with label
Animations & Props
Character animations and held props
Control Disabling
Disable movement, combat, vehicle controls
Cancellable
Player can cancel with keybind (X)
Client-Side Only: Progress bars run on the client. Always validate completion server-side.
Basic Usage
Progress
Simple progress bar with callback. Parameters:| Name | Type | Required | Description |
|---|---|---|---|
| action | table | Yes | Progress action configuration |
| finish | function | Yes | Callback when complete/cancelled |
-- Client side
Progress:Progress({
name = 'eating_food',
duration = 5000, -- 5 seconds
label = 'Eating...',
canCancel = true,
controlDisables = {
disableMovement = true,
disableCarMovement = true,
disableCombat = true,
},
animation = {
animDict = 'mp_player_inteat@burger',
anim = 'mp_player_int_eat_burger',
flags = 49,
},
}, function(cancelled)
if not cancelled then
-- Player finished eating
TriggerServerEvent('food:server:Consume', foodItem)
else
-- Player cancelled
print('Eating cancelled')
end
end)
Action Configuration
Required Fields
| Field | Type | Description |
|---|---|---|
| name | string | Unique action identifier |
| duration | number | Duration in milliseconds |
| label | string | Display text on progress bar |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
| useWhileDead | boolean | false | Allow when dead |
| canCancel | boolean | true | Player can cancel (X key) |
| ignoreModifier | boolean | false | Ignore speed modifiers |
| disarm | boolean | true | Holster weapon before action |
| controlDisables | table | Disable specific controls | |
| animation | table | nil | Character animation |
| prop | table | nil | Held prop (object) |
| propTwo | table | nil | Second held prop |
| vehicle | boolean | false | Must be in vehicle |
| tickrate | number | nil | Tick event interval (ms) |
Control Disables
Available Disable Options
controlDisables = {
disableMovement = false, -- Disable WASD, jump, sprint
disableCarMovement = false, -- Disable vehicle steering/acceleration
disableMouse = false, -- Disable camera movement
disableCombat = false, -- Disable shooting and melee
}
-- Lock player in place during action
controlDisables = {
disableMovement = true,
disableCarMovement = true,
disableMouse = false, -- Allow looking around
disableCombat = true,
}
Animations
Animation Dictionary
animation = {
animDict = 'anim@heists@ornate_bank@grab_cash',
anim = 'grab',
flags = 49,
}
1- Normal16- Cancellable (upper body only)32- Allow player control48- Upper body only, cancellable49- Upper body only, loop
Scenario Task
animation = {
task = 'PROP_HUMAN_BUM_BIN', -- Scenario name
}
Emote System
animation = {
anim = 'sit', -- Emote name from animation system
}
-- Lockpicking
animation = {
animDict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
anim = 'machinic_loop_mechandplayer',
flags = 16,
}
-- Eating
animation = {
animDict = 'mp_player_inteat@burger',
anim = 'mp_player_int_eat_burger',
flags = 49,
}
-- Drinking
animation = {
animDict = 'mp_player_intdrink',
anim = 'loop_bottle',
flags = 49,
}
-- Searching
animation = {
animDict = 'amb@prop_human_bum_bin@idle_b',
anim = 'idle_d',
flags = 1,
}
Props
Single Prop
prop = {
model = 'prop_phone_01',
bone = 57005, -- Bone index (default: 60309 = right hand)
coords = { x = 0.14, y = 0.01, z = 0.02 },
rotation = { x = 110.0, y = 120.0, z = 0.0 },
}
Two Props
-- Left and right hands
prop = {
model = 'prop_cs_burger_01',
bone = 60309, -- Right hand
coords = { x = 0.0, y = 0.0, z = 0.0 },
rotation = { x = 0.0, y = 0.0, z = 0.0 },
},
propTwo = {
model = 'prop_drink_champ',
bone = 18905, -- Left hand
coords = { x = 0.0, y = 0.0, z = 0.0 },
rotation = { x = 0.0, y = 0.0, z = 0.0 },
}
60309- Right hand (default)18905- Left hand28422- Left foot52301- Right foot24818- Head11816- Pelvis
-- Eating burger
prop = {
model = 'prop_cs_burger_01',
bone = 60309,
coords = { x = 0.0, y = 0.0, z = -0.02 },
rotation = { x = 0.0, y = 0.0, z = 0.0 },
}
-- Drinking water
prop = {
model = 'prop_ld_flow_bottle',
bone = 60309,
coords = { x = 0.03, y = 0.02, z = 0.02 },
rotation = { x = 240.0, y = -60.0, z = 0.0 },
}
-- Holding lockpick
prop = {
model = 'prop_tool_screwdvr01',
bone = 28422,
coords = { x = 0.0, y = 0.0, z = 0.0 },
rotation = { x = 0.0, y = 0.0, z = 0.0 },
}
Advanced Methods
ProgressWithStartEvent
Execute code when progress starts. Parameters:| Name | Type | Description |
|---|---|---|
| action | table | Progress configuration |
| start | function | Called when progress starts |
| finish | function | Called when complete/cancelled |
-- Client side
Progress:ProgressWithStartEvent({
name = 'lockpicking',
duration = 10000,
label = 'Lockpicking...',
canCancel = true,
controlDisables = {
disableMovement = true,
disableCombat = true,
},
}, function()
-- Start event - runs immediately
print('Lockpick attempt started')
TriggerServerEvent('lockpick:server:Start', doorId)
end, function(cancelled)
-- Finish event
if not cancelled then
TriggerServerEvent('lockpick:server:Success', doorId)
else
TriggerServerEvent('lockpick:server:Cancelled', doorId)
end
end)
ProgressWithTickEvent
Execute code repeatedly during progress. Parameters:| Name | Type | Description |
|---|---|---|
| action | table | Progress configuration (include tickrate) |
| tick | function | Called every tick |
| finish | function | Called when complete/cancelled |
-- Client side
local searchProgress = 0
Progress:ProgressWithTickEvent({
name = 'searching',
duration = 15000,
label = 'Searching...',
tickrate = 1000, -- Tick every 1 second
canCancel = true,
controlDisables = {
disableMovement = true,
},
}, function()
-- Tick event - runs every 1000ms
searchProgress = searchProgress + 6.67 -- 100% / 15 seconds
print('Search progress:', math.floor(searchProgress), '%')
-- Could trigger effects each tick
if math.random(100) <= 10 then
TriggerEvent('mythic-notifications:client:Send', {
message = 'Found something...', type = 'info'
})
end
end, function(cancelled)
if not cancelled then
-- Search complete
TriggerServerEvent('search:server:Complete', searchProgress)
end
searchProgress = 0
end)
ProgressWithStartAndTick
Combine start and tick events. Parameters:| Name | Type | Description |
|---|---|---|
| action | table | Progress configuration |
| start | function | Called when starts |
| tick | function | Called every tick |
| finish | function | Called when complete/cancelled |
-- Client side
local hackAttempts = 0
Progress:ProgressWithStartAndTick({
name = 'hacking',
duration = 20000,
label = 'Hacking...',
tickrate = 2000,
canCancel = false, -- Cannot cancel
controlDisables = {
disableMovement = true,
disableCarMovement = true,
disableCombat = true,
},
}, function()
-- Start
print('Hack started')
hackAttempts = 0
end, function()
-- Tick every 2 seconds
hackAttempts = hackAttempts + 1
print('Hack attempt:', hackAttempts)
-- Visual effect
SetTimecycleModifier('scanline_cam_cheap')
Wait(500)
ClearTimecycleModifier()
end, function(cancelled)
-- Finish
if not cancelled then
TriggerServerEvent('hack:server:Success', hackAttempts)
end
hackAttempts = 0
ClearTimecycleModifier()
end)
Utility Methods
CurrentAction
Get current active action name. Returns:| Type | Description |
|---|---|
| string | Current action name or nil |
-- Client side
local currentAction = Progress:CurrentAction()
if currentAction then
print('Currently doing:', currentAction)
else
print('No active action')
end
Cancel
Cancel current progress (if allowed). Parameters:| Name | Type | Required | Description |
|---|---|---|---|
| force | boolean | No | Force cancel even if canCancel is false |
-- Client side
-- Player pressed cancel keybind (auto-called)
Progress:Cancel()
-- Force cancel (admin, server-side request)
Progress:Cancel(true)
Fail
Immediately fail the current progress. Parameters:| Name | Type | Required | Description |
|---|---|---|---|
| None | - | - | - |
-- Client side
-- Fail if player loses required item mid-action
AddEventHandler('Inventory:Client:ItemRemoved', function(item)
local action = Progress:CurrentAction()
if action == 'using_lockpick' and item.name == 'lockpick' then
Progress:Fail()
end
end)
Finish
Immediately complete the current progress. Parameters:| Name | Type | Required | Description |
|---|---|---|---|
| None | - | - | - |
-- Client side (rarely used - usually let it finish naturally)
Progress:Finish()
Modifier
Apply speed modifier to progress bars. Parameters:| Name | Type | Required | Description |
|---|---|---|---|
| percentage | number | Yes | Speed percentage (100 = normal, 150 = 50% faster) |
| duration | number | Yes | How long modifier lasts (ms) |
| Type | Description |
|---|---|
| boolean | True if applied, false if modifier already active |
-- Client side
-- Speed buff (50% faster for 5 minutes)
if Progress:Modifier(150, 300000) then
print('Speed boost applied!')
TriggerEvent('mythic-notifications:client:Send', {
message = 'You feel energized (+50% action speed)',
type = 'success'
})
else
print('Speed modifier already active')
end
-- Slow debuff (50% slower for 30 seconds)
if Progress:Modifier(50, 30000) then
print('Slowed!')
end
- Only one modifier active at a time
- Actions with
ignoreModifier = trueskip this - Modifier persists across multiple actions until duration expires
Complete Examples
Crafting System
-- Client side
AddEventHandler('crafting:client:CraftItem', function(recipe)
local hasItems = Inventory.Check.Player:HasItems(recipe.materials)
if not hasItems then
Notification:Error('Missing materials')
return
end
Progress:Progress({
name = 'crafting_' .. recipe.result,
duration = recipe.time * 1000,
label = 'Crafting ' .. recipe.label .. '...',
canCancel = true,
controlDisables = {
disableMovement = true,
disableCombat = true,
},
animation = {
animDict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
anim = 'machinic_loop_mechandplayer',
flags = 49,
},
prop = {
model = 'prop_tool_screwdvr01',
bone = 60309,
coords = { x = 0.0, y = 0.0, z = 0.0 },
rotation = { x = 0.0, y = 0.0, z = 0.0 },
},
}, function(cancelled)
if not cancelled then
TriggerServerEvent('crafting:server:Complete', recipe.id)
end
end)
end)
Lockpicking
-- Client side
AddEventHandler('lockpick:client:Start', function(doorId)
if not Inventory.Check.Player:HasItem('lockpick', 1) then
Notification:Error('Need a lockpick')
return
end
local successChance = math.random(100)
Progress:ProgressWithTickEvent({
name = 'lockpicking_door',
duration = 15000,
label = 'Lockpicking...',
tickrate = 3000,
canCancel = true,
controlDisables = {
disableMovement = true,
disableCarMovement = true,
disableCombat = true,
},
animation = {
animDict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
anim = 'machinic_loop_mechandplayer',
flags = 16,
},
}, function()
-- Tick - chance to break lockpick
if math.random(100) <= 10 then
Notification:Warn('Lockpick slipped!')
if math.random(100) <= 30 then
-- Break lockpick
TriggerServerEvent('lockpick:server:Break')
Progress:Fail()
end
end
end, function(cancelled)
if not cancelled then
-- Check success
if successChance <= 60 then
TriggerServerEvent('doors:server:Unlock', doorId)
Notification:Success('Door unlocked')
else
TriggerServerEvent('lockpick:server:Break')
Notification:Error('Lockpick failed')
end
end
end)
end)
Medical Revive
-- Client side
AddEventHandler('ems:client:Revive', function(targetId)
local targetPed = GetPlayerPed(GetPlayerFromServerId(targetId))
local distance = #(GetEntityCoords(PlayerPedId()) - GetEntityCoords(targetPed))
if distance > 3.0 then
Notification:Error('Too far away')
return
end
Progress:Progress({
name = 'reviving_player',
duration = 10000,
label = 'Reviving patient...',
useWhileDead = false,
canCancel = false, -- Cannot cancel revive
controlDisables = {
disableMovement = true,
disableCombat = true,
},
animation = {
animDict = 'amb@medic@standing@kneel@base',
anim = 'base',
flags = 1,
},
prop = {
model = 'prop_ld_health_pack',
bone = 60309,
coords = { x = 0.0, y = 0.0, z = 0.0 },
rotation = { x = 0.0, y = 0.0, z = 0.0 },
},
}, function(cancelled)
if not cancelled then
-- Check if still close enough
local newDist = #(GetEntityCoords(PlayerPedId()) - GetEntityCoords(targetPed))
if newDist <= 3.0 then
TriggerServerEvent('ems:server:RevivePlayer', targetId)
else
Notification:Error('Patient moved too far')
end
end
end)
end)
Best Practices
Server Validation
-- ❌ BAD - Trust client
AddEventHandler('crafting:server:Complete', function(recipeId)
-- Client says they completed crafting
local src = source
GiveItem(src, recipeId) -- UNSAFE!
end)
-- ✅ GOOD - Validate on server
AddEventHandler('crafting:server:Complete', function(recipeId)
local src = source
local player = Fetch:Source(src)
if not player then return end
local char = player:GetData('Character')
local recipe = GetRecipe(recipeId)
-- Verify player still has materials
local stateId = char:GetData('SID')
-- Check all materials
local hasMaterials = true
for _, mat in ipairs(recipe.materials) do
if not Inventory.Items:Has(stateId, 1, mat.item, mat.count) then
hasMaterials = false
break
end
end
if hasMaterials then
-- Remove materials
for _, mat in ipairs(recipe.materials) do
Inventory.Items:Remove(stateId, 1, mat.item, mat.count)
end
-- Give result
Inventory:AddItem(stateId, recipe.result, 1)
end
end)
Distance Checks
-- Check distance before AND after progress
local startCoords = GetEntityCoords(PlayerPedId())
Progress:Progress({
-- ... config
}, function(cancelled)
if not cancelled then
local endCoords = GetEntityCoords(PlayerPedId())
local distance = #(startCoords - endCoords)
if distance > 5.0 then
Notification:Error('You moved too far')
return
end
-- Continue with action
TriggerServerEvent('action:server:Complete')
end
end)
Next Steps
Notifications
Toast notification system
Menus
Input forms and menu systems
HUD Exports
Main HUD controls
Inventory
Item system integration
Keybind: Players can cancel progress bars (if
canCancel = true) by pressing X (default cancel_action keybind).Dead Actions: By default, progress bars fail if the player dies. Set
useWhileDead = true only for specific cases like medical revival.