Skip to main content
Job events fire when characters change jobs, get promoted/demoted, or change duty status, allowing other resources to react to employment changes.

Server Events

All job events fire on the server-side.

Job Changes

Hired, Fired, JobChanged

Rank Changes

Promoted, Demoted

Duty Status

ClockedIn, ClockedOut

Paycheck

PaycheckIssued

Employment Events

mythic-jobs:server:JobChanged

Fired when a character’s job or grade changes.
AddEventHandler('mythic-jobs:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
    -- Handler code
end)
Parameters:
characterId
number
Character SID
newJob
string
New job identifier
newGrade
number
New job grade
oldJob
string
Previous job identifier
oldGrade
number
Previous job grade
Examples:
-- Log job changes
AddEventHandler('mythic-jobs:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
    Logger:Info('Jobs', 'Job changed', {
        console = true,
        file = true
    }, {
        character = characterId,
        from = oldJob .. ' (Grade ' .. oldGrade .. ')',
        to = newJob .. ' (Grade ' .. newGrade .. ')'
    })
end)

-- Give job-specific equipment
AddEventHandler('mythic-jobs:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
    local player = Fetch:SID(characterId)

    if not player then
        return
    end

    local source = player:GetData('Source')

    -- Remove old job items
    if oldJob == 'police' then
        Inventory.Items:Remove(characterId, 1, 'weapon_pistol', 999)
        Inventory.Items:Remove(characterId, 1, 'handcuffs', 999)
        Inventory.Items:Remove(characterId, 1, 'radio', 999)
    elseif oldJob == 'ems' then
        Inventory.Items:Remove(characterId, 1, 'medkit', 999)
        Inventory.Items:Remove(characterId, 1, 'bandage', 999)
    end

    -- Give new job items
    if newJob == 'police' then
        Inventory:AddItem(characterId, 'weapon_pistol', 1, {
            serial = GenerateSerial(),
            ammo = 12
        })
        Inventory:AddItem(characterId, 'handcuffs', 1)
        Inventory:AddItem(characterId, 'radio', 1)

        TriggerClientEvent('mythic-notifications:client:Send', source, {
            message = 'You received your police equipment',
            type = 'success'
        })
    elseif newJob == 'ems' then
        Inventory:AddItem(characterId, 'medkit', 5)
        Inventory:AddItem(characterId, 'bandage', 10)

        TriggerClientEvent('mythic-notifications:client:Send', source, {
            message = 'You received your EMS equipment',
            type = 'success'
        })
    end
end)

-- Update Discord roles
AddEventHandler('mythic-jobs:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
    local player = Fetch:SID(characterId)

    if player then
        local source = player:GetData('Source')
        -- Update Discord roles based on job
        TriggerEvent('discord:updateRoles', source, newJob, newGrade)
    end
end)

-- Grant/revoke permissions
AddEventHandler('mythic-jobs:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
    local player = Fetch:SID(characterId)

    if not player then
        return
    end

    local source = player:GetData('Source')

    local identifier = GetPlayerIdentifier(source, 0)

    -- Remove old job permissions
    if oldJob == 'police' then
        ExecuteCommand('remove_principal identifier.' .. identifier .. ' group.police')
    end

    -- Grant new job permissions
    if newJob == 'police' then
        ExecuteCommand('add_principal identifier.' .. identifier .. ' group.police')
    end
end)

mythic-jobs:server:Hired

Fired when a character gets hired (changes from unemployed to employed).
AddEventHandler('mythic-jobs:server:Hired', function(source, characterId, jobName, grade)
    -- Handler code
end)
Parameters:
source
number
Player server ID
characterId
number
Character SID
jobName
string
New job identifier
grade
number
Starting grade
Examples:
-- Welcome message on hire
AddEventHandler('mythic-jobs:server:Hired', function(source, characterId, jobName, grade)
    local jobLabel = jobName
    local gradeLabel = Jobs:GetGradeLabel(jobName, grade)

    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'Welcome to ' .. jobLabel .. ' as ' .. gradeLabel,
        type = 'success',
        duration = 5000
    })
end)

-- Track employment history
AddEventHandler('mythic-jobs:server:Hired', function(source, characterId, jobName, grade)
    Database.Game:insertOne({
        collection = 'employment_history',
        document = {
            character = characterId,
            job = jobName,
            grade = grade,
            hiredAt = os.time()
        }
    })
end)

-- Achievement tracking
AddEventHandler('mythic-jobs:server:Hired', function(source, characterId, jobName, grade)
    Achievements:Unlock(characterId, 'first_job')

    if jobName == 'police' then
        Achievements:Unlock(characterId, 'joined_police')
    end
end)

mythic-jobs:server:Fired

Fired when a character gets fired (changes to unemployed).
AddEventHandler('mythic-jobs:server:Fired', function(source, characterId, oldJob, oldGrade)
    -- Handler code
end)
Parameters:
source
number
Player server ID
characterId
number
Character SID
oldJob
string
Previous job identifier
oldGrade
number
Previous grade
Examples:
-- Remove job items on fire
AddEventHandler('mythic-jobs:server:Fired', function(source, characterId, oldJob, oldGrade)
    RemoveJobItems(source, oldJob)

    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'You have been fired',
        type = 'error'
    })
end)

-- Clock out on fire
AddEventHandler('mythic-jobs:server:Fired', function(source, characterId, oldJob, oldGrade)
    Jobs.Duty:Off(source, jobName)
end)

-- Update employment history
AddEventHandler('mythic-jobs:server:Fired', function(source, characterId, oldJob, oldGrade)
    Database.Game:updateOne({
        collection = 'employment_history',
        query = { character = characterId, job = oldJob, firedAt = nil },
        update = { ['$set'] = { firedAt = os.time() } }
    })
end)

Rank Events

mythic-jobs:server:Promoted

Fired when a character is promoted (grade increased).
AddEventHandler('mythic-jobs:server:Promoted', function(source, characterId, jobName, newGrade, oldGrade)
    -- Handler code
end)
Examples:
-- Congratulate on promotion
AddEventHandler('mythic-jobs:server:Promoted', function(source, characterId, jobName, newGrade, oldGrade)
    local jobLabel = jobName
    local gradeLabel = Jobs:GetGradeLabel(jobName, newGrade)

    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'Promoted to ' .. gradeLabel,
        type = 'success'
    })

    -- Bonus for promotion (add to cash)
    local player = Fetch:SID(characterId)
    if player then
        local char = player:GetData('Character')
        local bonus = 1000 * newGrade
        local currentCash = char:GetData('Cash')
        char:SetData('Cash', currentCash + bonus)
    end
end)

-- Update permissions on promotion
AddEventHandler('mythic-jobs:server:Promoted', function(source, characterId, jobName, newGrade, oldGrade)
    if jobName == 'police' then
        -- Higher ranks get more permissions
        if newGrade >= 3 then
            -- Sergeants can access armory
            TriggerClientEvent('mythic-police:client:GrantArmoryAccess', source, true)
        end

        if newGrade >= 4 then
            -- Lieutenants can manage officers
            TriggerClientEvent('mythic-police:client:GrantManagementAccess', source, true)
        end
    end
end)

mythic-jobs:server:Demoted

Fired when a character is demoted (grade decreased).
AddEventHandler('mythic-jobs:server:Demoted', function(source, characterId, jobName, newGrade, oldGrade)
    -- Handler code
end)
Examples:
-- Notify on demotion
AddEventHandler('mythic-jobs:server:Demoted', function(source, characterId, jobName, newGrade, oldGrade)
    local gradeLabel = Jobs:GetGradeLabel(jobName, newGrade)

    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'Demoted to ' .. gradeLabel,
        type = 'warning'
    })
end)

-- Revoke permissions on demotion
AddEventHandler('mythic-jobs:server:Demoted', function(source, characterId, jobName, newGrade, oldGrade)
    if jobName == 'police' then
        if newGrade < 3 then
            TriggerClientEvent('mythic-police:client:GrantArmoryAccess', source, false)
        end

        if newGrade < 4 then
            TriggerClientEvent('mythic-police:client:GrantManagementAccess', source, false)
        end
    end
end)

Duty Status Events

mythic-jobs:server:ClockedIn

Fired when a character clocks in (goes on duty).
AddEventHandler('mythic-jobs:server:ClockedIn', function(source, characterId, jobName)
    -- Handler code
end)
Examples:
-- Notify on clock in
AddEventHandler('mythic-jobs:server:ClockedIn', function(source, characterId, jobName)
    local jobLabel = jobName

    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'Clocked in as ' .. jobLabel,
        type = 'info'
    })
end)

-- Give job equipment
AddEventHandler('mythic-jobs:server:ClockedIn', function(source, characterId, jobName)
    if jobName == 'police' then
        -- Spawn police vehicle
        TriggerEvent('mythic-vehicles:server:SpawnJobVehicle', source, 'police')
    elseif jobName == 'taxi' then
        TriggerEvent('mythic-vehicles:server:SpawnJobVehicle', source, 'taxi')
    end
end)

-- Update HUD
AddEventHandler('mythic-jobs:server:ClockedIn', function(source, characterId, jobName)
    TriggerClientEvent('mythic-hud:client:SetDuty', source, true)
end)

-- Notify coworkers
AddEventHandler('mythic-jobs:server:ClockedIn', function(source, characterId, jobName)
    local player = Fetch:SID(characterId)

    if not player then
        return
    end

    local char = player:GetData('Character')
    if not char then
        return
    end

    local dutyData = Jobs.Duty:GetDutyData(jobName)

    for _, coworkerSource in ipairs(dutyData.DutyPlayers) do
        if coworkerSource ~= source then
                TriggerClientEvent('mythic-notifications:client:Send', coworkerSource, {
                    message = char:GetData('First') .. ' ' .. char:GetData('Last') .. ' is now on duty',
                    type = 'info'
                })
            end
        end
    end
end)

mythic-jobs:server:ClockedOut

Fired when a character clocks out (goes off duty).
AddEventHandler('mythic-jobs:server:ClockedOut', function(source, characterId, jobName)
    -- Handler code
end)
Examples:
-- Remove job equipment
AddEventHandler('mythic-jobs:server:ClockedOut', function(source, characterId, jobName)
    -- Remove job vehicles
    TriggerEvent('mythic-vehicles:server:RemoveJobVehicles', source)

    -- End active tasks
    TriggerEvent('mythic-jobs:server:EndActiveTasks', source)

    -- Notify
    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'Clocked out',
        type = 'info'
    })
end)

-- Update HUD
AddEventHandler('mythic-jobs:server:ClockedOut', function(source, characterId, jobName)
    TriggerClientEvent('mythic-hud:client:SetDuty', source, false)
end)

-- Track duty time
local dutyStartTimes = {}

AddEventHandler('mythic-jobs:server:ClockedIn', function(source, characterId, jobName)
    dutyStartTimes[characterId] = os.time()
end)

AddEventHandler('mythic-jobs:server:ClockedOut', function(source, characterId, jobName)
    if dutyStartTimes[characterId] then
        local dutyTime = os.time() - dutyStartTimes[characterId]

        -- Update total duty time
        Characters:UpdateCharacter(characterId, {
            ['metadata.stats.dutyTime'] = (char.metadata.stats.dutyTime or 0) + dutyTime
        })

        dutyStartTimes[characterId] = nil
    end
end)

Paycheck Events

mythic-jobs:server:PaycheckIssued

Fired when a paycheck is issued to a character.
AddEventHandler('mythic-jobs:server:PaycheckIssued', function(source, characterId, jobName, amount)
    -- Handler code
end)
Examples:
-- Notify on paycheck
AddEventHandler('mythic-jobs:server:PaycheckIssued', function(source, characterId, jobName, amount)
    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'Paycheck deposited: $' .. amount,
        type = 'success'
    })
end)

-- Log paychecks
AddEventHandler('mythic-jobs:server:PaycheckIssued', function(source, characterId, jobName, amount)
    Database.Game:insertOne({
        collection = 'paycheck_log',
        document = {
            character = characterId,
            job = jobName,
            amount = amount,
            timestamp = os.time()
        }
    })
end)

-- Tax system
AddEventHandler('mythic-jobs:server:PaycheckIssued', function(source, characterId, jobName, amount)
    local taxRate = 0.15  -- 15% tax

    local taxAmount = math.floor(amount * taxRate)
    local netPay = amount - taxAmount

    -- Deduct tax from character's cash
    local player = Fetch:SID(characterId)
    if player then
        local char = player:GetData('Character')
        local currentCash = char:GetData('Cash')
        char:SetData('Cash', currentCash - taxAmount)
    end

    -- Add to government funds
    Database.Game:updateOne({
        collection = 'government',
        query = {},
        update = { ['$inc'] = { funds = taxAmount } }
    })

    TriggerClientEvent('mythic-notifications:client:Send', source, {
        message = 'Tax deducted: $' .. taxAmount,
        type = 'info'
    })
end)

Client Events

mythic-jobs:client:SetJob

Sent to client when job changes.
AddEventHandler('mythic-jobs:client:SetJob', function(jobData)
    -- Handler code
end)
Examples:
-- Update HUD with new job
AddEventHandler('mythic-jobs:client:SetJob', function(jobData)
    SendNUIMessage({
        type = 'UPDATE_JOB',
        job = {
            label = jobData.jobLabel,
            grade = jobData.gradeLabel,
            onDuty = jobData.onDuty
        }
    })
end)

-- Load job-specific blips
AddEventHandler('mythic-jobs:client:SetJob', function(jobData)
    -- Remove old blips
    RemoveJobBlips()

    -- Add new job blips
    if jobData.job == 'police' then
        AddPoliceBlips()
    elseif jobData.job == 'ems' then
        AddEMSBlips()
    end
end)

mythic-jobs:client:SetDuty

Sent to client when duty status changes.
AddEventHandler('mythic-jobs:client:SetDuty', function(onDuty)
    -- Handler code
end)
Examples:
-- Update HUD
AddEventHandler('mythic-jobs:client:SetDuty', function(onDuty)
    SendNUIMessage({
        type = 'SET_DUTY',
        onDuty = onDuty
    })
end)

-- Change ped model for uniforms
AddEventHandler('mythic-jobs:client:SetDuty', function(onDuty)
    if onDuty then
        -- Put on uniform
        TriggerEvent('mythic-clothing:client:LoadUniform')
    else
        -- Change to civilian clothes
        TriggerEvent('mythic-clothing:client:LoadCivilian')
    end
end)

Using Events for Custom Logic

Custom Job Perks

-- Police get free repairs
AddEventHandler('mythic-jobs:server:ClockedIn', function(source, characterId, jobName)
    if jobName == 'police' then
        -- Grant free repair perk
        TriggerEvent('mythic-customs:server:GrantFreePerk', source, 'repairs')
    end
end)

AddEventHandler('mythic-jobs:server:ClockedOut', function(source, characterId, jobName)
    if jobName == 'police' then
        -- Remove free repair perk
        TriggerEvent('mythic-customs:server:RemoveFreePerk', source, 'repairs')
    end
end)

Job-Based Pricing

-- Mechanics get discounts at parts shops
AddEventHandler('mythic-shops:server:Purchase', function(source, item, price)
    local player = Fetch:Source(source)

    if not player then
        return price
    end

    local char = player:GetData('Character')
    if not char then
        return price
    end

    local stateId = char:GetData('SID')
    local jobData = char:GetData('Jobs')

    if jobData and jobData.job == 'mechanic' then
        -- 25% discount for mechanics
        local discount = math.floor(price * 0.25)
        local finalPrice = price - discount

        TriggerClientEvent('mythic-notifications:client:Send', source, {
            message = 'Employee discount: $' .. discount,
            type = 'success'
        })

        return finalPrice
    end

    return price
end)

Automatic Clock Out

-- Auto clock out on disconnect
AddEventHandler('playerDropped', function()
    local player = Fetch:Source(source)

    if not player then
        return
    end

    local char = player:GetData('Character')
    if char then
        local stateId = char:GetData('SID')
        local jobData = char:GetData('Jobs')

        if jobData and jobData.onDuty then
            Jobs.Duty:Off(source, jobId)

            Logger:Info('Jobs', 'Auto clocked out', {
                console = true,
                file = true
            }, {
                character = stateId,
                job = jobData.job
            })
        end
    end
end)

-- Auto clock out after inactivity
local lastActivity = {}

CreateThread(function()
    while true do
        Wait(60000)  -- Check every minute

        for _, playerId in ipairs(GetPlayers()) do
            local source = tonumber(playerId)
            local player = Fetch:Source(source)

            if player then
                local char = player:GetData('Character')
                if not char then
                    goto continue
                end

                local stateId = char:GetData('SID')
                local jobData = char:GetData('Jobs')

                if jobData and jobData.onDuty then
                    local inactive = (os.time() - (lastActivity[source] or os.time())) > 1800  -- 30 min

                    if inactive then
                        Jobs.Duty:Off(source, jobId)

                        TriggerClientEvent('mythic-notifications:client:Send', source, {
                            message = 'Clocked out due to inactivity',
                            type = 'warning'
                        })
                    end
                end
            end

            ::continue::
        end
    end
end)

Best Practices

AddEventHandler('mythic-jobs:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
    local player = Fetch:SID(characterId)

    if not player then
        return
    end

    local char = player:GetData('Character')
    if not char then
        return
    end

    -- Process event
end)
local jobTracking = {}

AddEventHandler('playerDropped', function()
    local player = Fetch:Source(source)

    if player then
        local char = player:GetData('Character')
        if char then
            local stateId = char:GetData('SID')
            jobTracking[stateId] = nil
        end
    end
end)
AddEventHandler('mythic-jobs:server:ClockedIn', function(source, characterId, jobName)
    -- Source may be nil if triggered programmatically
    if source then
        TriggerClientEvent('notify', source, 'Clocked in')
    end
end)

Next Steps

Jobs - Exports

Job management methods

Jobs - Configuration

Job definitions

Characters Events

Character events

Job System Guide

Complete job system
Event Timing: Job events fire after the database is updated, so you can safely query the character’s new job data within event handlers.