docsresourcesQb Policejob

QB-PoliceJob - Police Department System

The qb-policejob resource provides a comprehensive police department system for QBCore servers, featuring MDT integration, evidence management, vehicle impounding, arrest mechanics, and complete law enforcement operations.

Overview

QB-PoliceJob creates a realistic police experience with advanced systems for investigations, arrests, evidence collection, and department management. Officers can access mobile data terminals, manage evidence, process arrests, and coordinate law enforcement activities.

Key Features

  • Mobile Data Terminal (MDT): Complete police database and dispatch system
  • Evidence System: Crime scene investigation and evidence management
  • Vehicle Impound: Vehicle seizure and storage system
  • Arrest Mechanics: Handcuffs, booking, and jail processing
  • Police Vehicles: Emergency vehicles with equipment
  • Weapon Management: Police armory and weapon access
  • Radar System: Speed detection and traffic enforcement
  • Department Ranks: Hierarchical police structure
  • Emergency Alerts: Automatic dispatch system

Installation

Prerequisites

  • QBCore Framework
  • qb-target (for interaction system)
  • qb-menu (for police menus)
  • qb-input (for police forms)
  • qb-inventory (for police equipment)
  • qb-garage (for police vehicles)

Installation Steps

  1. Download the Resource
cd resources/[qb]
git clone https://github.com/qbcore-framework/qb-policejob.git
  1. Database Setup
-- Police department tables
CREATE TABLE IF NOT EXISTS `police_reports` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `citizenid` varchar(50) DEFAULT NULL,
  `officer` varchar(50) DEFAULT NULL,
  `incident_type` varchar(100) DEFAULT NULL,
  `description` longtext DEFAULT NULL,
  `evidence` longtext DEFAULT NULL,
  `date` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `police_evidence` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(50) DEFAULT NULL,
  `identifier` varchar(50) DEFAULT NULL,
  `description` text DEFAULT NULL,
  `data` longtext DEFAULT NULL,
  `created_by` varchar(50) DEFAULT NULL,
  `created_at` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `police_impound` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `citizenid` varchar(50) DEFAULT NULL,
  `vehicle` varchar(50) DEFAULT NULL,
  `plate` varchar(50) DEFAULT NULL,
  `reason` varchar(255) DEFAULT NULL,
  `fine` int(11) DEFAULT 0,
  `officer` varchar(50) DEFAULT NULL,
  `date` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `police_alerts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(50) DEFAULT NULL,
  `coords` varchar(255) DEFAULT NULL,
  `description` text DEFAULT NULL,
  `code` varchar(10) DEFAULT NULL,
  `units_responding` int(11) DEFAULT 0,
  `created_at` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `player_warnings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `citizenid` varchar(50) DEFAULT NULL,
  `officer` varchar(50) DEFAULT NULL,
  `warning` text DEFAULT NULL,
  `date` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
  1. Add Items to qb-core/shared/items.lua
-- Police Equipment
['handcuffs'] = {
    ['name'] = 'handcuffs',
    ['label'] = 'Handcuffs',
    ['weight'] = 500,
    ['type'] = 'item',
    ['image'] = 'handcuffs.png',
    ['unique'] = false,
    ['useable'] = true,
    ['shouldClose'] = true,
    ['combinable'] = nil,
    ['description'] = 'Police handcuffs for arrests'
},
['police_stormram'] = {
    ['name'] = 'police_stormram',
    ['label'] = 'Storm Ram',
    ['weight'] = 5000,
    ['type'] = 'item',
    ['image'] = 'police_stormram.png',
    ['unique'] = false,
    ['useable'] = true,
    ['shouldClose'] = true,
    ['combinable'] = nil,
    ['description'] = 'Breaching tool for forced entry'
},
['breathalyzer'] = {
    ['name'] = 'breathalyzer',
    ['label'] = 'Breathalyzer',
    ['weight'] = 200,
    ['type'] = 'item',
    ['image'] = 'breathalyzer.png',
    ['unique'] = false,
    ['useable'] = true,
    ['shouldClose'] = true,
    ['combinable'] = nil,
    ['description'] = 'Device to test blood alcohol content'
},
['evidence_bag'] = {
    ['name'] = 'evidence_bag',
    ['label'] = 'Evidence Bag',
    ['weight'] = 50,
    ['type'] = 'item',
    ['image'] = 'evidence_bag.png',
    ['unique'] = true,
    ['useable'] = false,
    ['shouldClose'] = false,
    ['combinable'] = nil,
    ['description'] = 'Sealed evidence bag'
}
  1. Add Job Configuration to qb-core/shared/jobs.lua
['police'] = {
    label = 'Law Enforcement',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        ['0'] = {
            name = 'Recruit',
            payment = 50
        },
        ['1'] = {
            name = 'Officer',
            payment = 75
        },
        ['2'] = {
            name = 'Detective',
            payment = 100
        },
        ['3'] = {
            name = 'Sergeant',
            payment = 125,
            isboss = true
        },
        ['4'] = {
            name = 'Lieutenant',
            payment = 150,
            isboss = true
        },
        ['5'] = {
            name = 'Captain',
            payment = 175,
            isboss = true
        },
        ['6'] = {
            name = 'Chief',
            payment = 200,
            isboss = true
        }
    }
}
  1. Add to server.cfg
ensure qb-policejob

Ensure qb-policejob loads after qb-core, qb-target, and qb-menu in your server.cfg

Configuration

Basic Configuration

The main configuration file is located at config.lua:

Config = {}
 
-- General Settings
Config.UseTarget = true
Config.DutyTime = true
Config.EnableESX = false
Config.MaxSpikes = 5
Config.HandCuffItem = "handcuffs"
 
-- Police Station Locations
Config.Locations = {
    ["duty"] = {
        vector3(441.7989, -982.0529, 30.6896),
        vector3(-449.811, 6014.0146, 31.716)
    },
    ["vehicle"] = {
        vector4(454.6, -1017.4, 28.4, 90.654),
        vector4(441.0, -1024.2, 28.3, 268.025),
        vector4(426.68, -1026.67, 28.26, 269.71)
    },
    ["stash"] = {
        vector3(453.21, -982.28, 30.68),
        vector3(-436.14, 5998.52, 31.71)
    },
    ["impound"] = {
        vector4(436.68, -1016.62, 27.75, 180.0),
        vector4(-449.35, 6019.8, 31.34, 135.0)
    },
    ["helicopter"] = {
        vector4(449.168, -981.325, 43.6918, 87.234),
        vector4(-475.43, 5988.353, 31.716, 31.34)
    },
    ["armory"] = {
        vector3(462.23, -981.12, 30.68),
        vector3(-439.593, 5998.525, 31.716)
    },
    ["trash"] = {
        vector3(453.95, -972.17, 30.68),
        vector3(-431.928, 5998.525, 31.509)
    },
    ["fingerprint"] = {
        vector3(460.4, -989.55, 30.68),
        vector3(-442.195, 6003.641, 31.716)
    },
    ["evidence"] = {
        vector3(475.52, -990.18, 26.27),
        vector3(-442.195, 6003.641, 31.716)
    }
}
 
-- Vehicle Spawn Points
Config.VehicleSpawns = {
    [1] = vector4(454.6, -1017.4, 28.4, 90.654),
    [2] = vector4(441.0, -1024.2, 28.3, 268.025),
    [3] = vector4(426.68, -1026.67, 28.26, 269.71),
    [4] = vector4(405.94, -1024.05, 28.25, 8.32)
}
 
-- Police Vehicles
Config.Vehicles = {
    ['police'] = {
        label = 'Police Cruiser',
        category = 'police',
        spawncost = 0,
        livery = {
            [0] = 'Police',
            [1] = 'Sheriff',
            [2] = 'State Police'
        }
    },
    ['police2'] = {
        label = 'Police Buffalo',
        category = 'police',
        spawncost = 0
    },
    ['police3'] = {
        label = 'Police Interceptor',
        category = 'police',
        spawncost = 100
    },
    ['policeb'] = {
        label = 'Police Bike',
        category = 'police',
        spawncost = 0
    },
    ['polmav'] = {
        label = 'Police Maverick',
        category = 'helicopter',
        spawncost = 500
    }
}

Evidence System Configuration

-- Evidence Types
Config.EvidenceTypes = {
    ["dna"] = {
        label = "DNA Sample",
        timeout = 10800000, -- 3 hours
        required_job = "police"
    },
    ["fingerprint"] = {
        label = "Fingerprint",
        timeout = 7200000, -- 2 hours
        required_job = "police"
    },
    ["bullet"] = {
        label = "Bullet Casing",
        timeout = 14400000, -- 4 hours
        required_job = "police"
    },
    ["blood"] = {
        label = "Blood Sample",
        timeout = 10800000, -- 3 hours
        required_job = "police"
    }
}
 
-- Crime Scene Settings
Config.CrimeScene = {
    evidenceTimeout = 20 * 60 * 1000, -- 20 minutes
    maxEvidence = 10,
    searchRadius = 2.5
}

Arrest System Configuration

-- Arrest Settings
Config.Arrests = {
    jailTime = 300, -- 5 minutes default
    maxJailTime = 3600, -- 1 hour maximum
    minJailTime = 60, -- 1 minute minimum
    jailLocation = vector3(1642.5, 2570.75, 45.56),
    releaseLocation = vector3(1836.98, 2584.95, 46.02)
}
 
-- Fine System
Config.Fines = {
    speeding = 250,
    reckless_driving = 500,
    drunk_driving = 1000,
    assault = 750,
    robbery = 2500,
    drug_possession = 1500
}

API Reference

Client Exports

CuffPlayer

Handcuff or uncuff a player.

-- Cuff a player
exports['qb-policejob']:CuffPlayer(targetPlayerId, true)
 
-- Uncuff a player
exports['qb-policejob']:CuffPlayer(targetPlayerId, false)
 
-- Parameters:
-- targetId: Target player server ID
-- cuffed: Boolean (true = cuff, false = uncuff)

EscortPlayer

Escort a handcuffed player.

-- Start escorting
exports['qb-policejob']:EscortPlayer(targetPlayerId, true)
 
-- Stop escorting
exports['qb-policejob']:EscortPlayer(targetPlayerId, false)
 
-- Parameters:
-- targetId: Target player server ID
-- escort: Boolean (true = start, false = stop)

CollectEvidence

Collect evidence at a location.

exports['qb-policejob']:CollectEvidence(evidenceType, coords)
 
-- Parameters:
-- evidenceType: Type of evidence ('dna', 'fingerprint', 'bullet', 'blood')
-- coords: Vector3 coordinates of evidence

Server Exports

JailPlayer

Send a player to jail.

exports['qb-policejob']:JailPlayer(source, time, reason)
 
-- Parameters:
-- source: Player server ID
-- time: Jail time in seconds
-- reason: Reason for jailing

FinePlayer

Issue a fine to a player.

exports['qb-policejob']:FinePlayer(source, amount, reason)
 
-- Parameters:
-- source: Player server ID
-- amount: Fine amount
-- reason: Reason for fine

CreateReport

Create a police report.

local reportId = exports['qb-policejob']:CreateReport(data)
 
-- Parameters:
-- data: {
--   citizenid = string,
--   officer = string,
--   incident_type = string,
--   description = string,
--   evidence = table (optional)
-- }

ImpoundVehicle

Impound a vehicle.

exports['qb-policejob']:ImpoundVehicle(plate, reason, fine, officer)
 
-- Parameters:
-- plate: Vehicle plate number
-- reason: Reason for impound
-- fine: Fine amount
-- officer: Officer name/ID

Events

Client Events

-- Player cuffed/uncuffed
RegisterNetEvent('qb-policejob:client:cuffed', function(cuffed)
    -- Handle cuff state change
end)
 
-- Player escorted
RegisterNetEvent('qb-policejob:client:escorted', function(escorted)
    -- Handle escort state change
end)
 
-- Police alert received
RegisterNetEvent('qb-policejob:client:policeAlert', function(alertData)
    -- Handle police alert
end)
 
-- Vehicle impounded
RegisterNetEvent('qb-policejob:client:vehicleImpounded', function(plate)
    -- Handle vehicle impound
end)

Server Events

-- Officer on/off duty
RegisterNetEvent('qb-policejob:server:dutyToggle', function(onDuty)
    -- Handle duty status change
end)
 
-- Arrest processed
RegisterNetEvent('qb-policejob:server:arrestPlayer', function(targetId, time, reason)
    -- Handle arrest processing
end)
 
-- Evidence collected
RegisterNetEvent('qb-policejob:server:evidenceCollected', function(evidence)
    -- Handle evidence collection
end)
 
-- Fine issued
RegisterNetEvent('qb-policejob:server:finePlayer', function(targetId, amount, reason)
    -- Handle fine processing
end)

Usage Examples

Basic Arrest System

-- Client-side arrest command
RegisterCommand('arrest', function(source, args)
    if QBCore.Functions.GetPlayerData().job.name == "police" and QBCore.Functions.GetPlayerData().job.onduty then
        local player, distance = QBCore.Functions.GetClosestPlayer()
        if player ~= -1 and distance < 2.0 then
            local targetId = GetPlayerServerId(player)
            local time = tonumber(args[1]) or 300
            local reason = table.concat(args, " ", 2) or "No reason provided"
            
            TriggerServerEvent('qb-policejob:server:arrestPlayer', targetId, time, reason)
        else
            QBCore.Functions.Notify("No player nearby", "error")
        end
    end
end)
 
-- Server-side arrest handler
RegisterNetEvent('qb-policejob:server:arrestPlayer', function(targetId, time, reason)
    local src = source
    local player = QBCore.Functions.GetPlayer(src)
    local target = QBCore.Functions.GetPlayer(targetId)
    
    if player.PlayerData.job.name == "police" and target then
        exports['qb-policejob']:JailPlayer(targetId, time, reason)
        TriggerClientEvent('QBCore:Notify', src, 'Player arrested for ' .. time .. ' seconds', 'success')
        TriggerClientEvent('QBCore:Notify', targetId, 'You have been arrested for: ' .. reason, 'error')
    end
end)

Evidence Collection System

-- Evidence collection near dead bodies
RegisterNetEvent('qb-policejob:client:collectDNA', function()
    local player = QBCore.Functions.GetPlayerData()
    if player.job.name == "police" and player.job.onduty then
        local ped = PlayerPedId()
        local coords = GetEntityCoords(ped)
        
        QBCore.Functions.Progressbar("collecting_dna", "Collecting DNA evidence...", 10000, false, true, {
            disableMovement = true,
            disableCarMovement = true,
            disableMouse = false,
            disableCombat = true,
        }, {
            animDict = "amb@medic@standing@kneel@base",
            anim = "base",
        }, {}, {}, function()
            TriggerServerEvent('qb-policejob:server:collectEvidence', 'dna', coords)
        end)
    end
end)
 
-- Server-side evidence processing
RegisterNetEvent('qb-policejob:server:collectEvidence', function(evidenceType, coords)
    local src = source
    local player = QBCore.Functions.GetPlayer(src)
    
    if player.PlayerData.job.name == "police" then
        local evidenceId = math.random(10000, 99999)
        local evidenceData = {
            id = evidenceId,
            type = evidenceType,
            coords = coords,
            officer = player.PlayerData.charinfo.firstname .. " " .. player.PlayerData.charinfo.lastname,
            time = os.date("%Y-%m-%d %H:%M:%S")
        }
        
        -- Store in database
        MySQL.insert('INSERT INTO police_evidence (type, identifier, description, data, created_by) VALUES (?, ?, ?, ?, ?)', {
            evidenceType,
            evidenceId,
            'Evidence collected at scene',
            json.encode(evidenceData),
            player.PlayerData.citizenid
        })
        
        -- Give evidence bag item
        player.Functions.AddItem('evidence_bag', 1, false, evidenceData)
        TriggerClientEvent('QBCore:Notify', src, 'Evidence collected: ' .. evidenceType, 'success')
    end
end)

MDT Integration

-- MDT search function
function SearchMDT(query, searchType)
    local results = {}
    
    if searchType == "person" then
        -- Search for person by name or citizenid
        local players = MySQL.query.await('SELECT * FROM players WHERE JSON_EXTRACT(charinfo, "$.firstname") LIKE ? OR JSON_EXTRACT(charinfo, "$.lastname") LIKE ? OR citizenid = ?', {
            '%' .. query .. '%',
            '%' .. query .. '%',
            query
        })
        
        for i = 1, #players do
            local charinfo = json.decode(players[i].charinfo)
            table.insert(results, {
                name = charinfo.firstname .. " " .. charinfo.lastname,
                citizenid = players[i].citizenid,
                phone = charinfo.phone,
                dob = charinfo.birthdate
            })
        end
    elseif searchType == "vehicle" then
        -- Search for vehicle by plate
        local vehicles = MySQL.query.await('SELECT * FROM player_vehicles WHERE plate LIKE ?', {
            '%' .. query .. '%'
        })
        
        for i = 1, #vehicles do
            table.insert(results, {
                plate = vehicles[i].plate,
                vehicle = vehicles[i].vehicle,
                owner = vehicles[i].citizenid
            })
        end
    end
    
    return results
end

Troubleshooting

Common Issues

Handcuffs Not Working

-- Check if player has handcuff item
local handcuffs = QBCore.Functions.HasItem('handcuffs')
if not handcuffs then
    QBCore.Functions.Notify("You don't have handcuffs", "error")
    return
end

Vehicle Spawn Issues

  • Verify spawn point coordinates are correct
  • Check if vehicle models exist in the server
  • Ensure proper permissions for vehicle spawning

Evidence System Not Working

-- Debug evidence collection
RegisterNetEvent('qb-policejob:debug:evidence', function()
    local evidence = MySQL.query.await('SELECT * FROM police_evidence ORDER BY created_at DESC LIMIT 10')
    for i = 1, #evidence do
        print("Evidence ID: " .. evidence[i].id .. ", Type: " .. evidence[i].type)
    end
end)

Debug Commands

-- Check player police status
/checkpolice [player_id]
 
-- Force jail player (admin only)
/forcejail [player_id] [time] [reason]
 
-- Clear all evidence (admin only)
/clearevidence
 
-- Toggle police radar
/radar

Performance Optimization

  1. Evidence Cleanup: Regularly clean expired evidence from database
  2. Alert System: Limit alert frequency to prevent spam
  3. Vehicle Management: Despawn unused police vehicles automatically
⚠️

Test all arrest and evidence systems thoroughly in a development environment before deploying to production.