Security HUD
System Watchdog
×
Threads Scanned
-- / --
SYS. LOAD --%
AI SHIELD ACTIVE
DMCA Policy
×

📋 DMCA Compliance

This platform and community fully complies with the Digital Millennium Copyright Act (DMCA) and international copyright laws. We take all copyright protection seriously.

🛡️ Copyright Protection

If you believe a posted item belongs to you or violates your copyright, you may file a DMCA takedown request through our official channels. Upon receiving a valid claim, the infringing content will be removed within 24 hours.

What's new
×
Fiveguard

Script [ESX/QB] Wasabi Police [leak]

aroufgang

Gold Elite
Joined
Apr 6, 2024
Messages
970
Reaction score
901
Points
201
Location
Paris
A truly immersive experience for your LEO players and criminals alike. We have formed this police job with many features requested from our community members such as resisting arrest, tackling, multiple LEO stations, animations, and much more. Our police job has received frequent updates and attention since it released more than one year ago! Wasabi Advanced Police Job is the most used and trusted police job outside the default ESX/QBCore police jobs.

1718832368236.png




Download :
You don't have permission to view content!
 
Last edited by a moderator:

aroufgang

Gold Elite
Joined
Apr 6, 2024
Messages
970
Reaction score
901
Points
201
Location
Paris
A truly immersive experience for your LEO players and criminals alike. We have formed this police job with many features requested from our community members such as resisting arrest, tackling, multiple LEO stations, animations, and much more. Our police job has received frequent updates and attention since it released more than one year ago! Wasabi Advanced Police Job is the most used and trusted police job outside the default ESX/QBCore police jobs.

View attachment 27781




Download :
You don't have permission to view content!
 

aroufgang

Gold Elite
Joined
Apr 6, 2024
Messages
970
Reaction score
901
Points
201
Location
Paris
A truly immersive experience for your LEO players and criminals alike. We have formed this police job with many features requested from our community members such as resisting arrest, tackling, multiple LEO stations, animations, and much more. Our police job has received frequent updates and attention since it released more than one year ago! Wasabi Advanced Police Job is the most used and trusted police job outside the default ESX/QBCore police jobs.

View attachment 27781




Download :
You don't have permission to view content!
 

aroufgang

Gold Elite
Joined
Apr 6, 2024
Messages
970
Reaction score
901
Points
201
Location
Paris
A truly immersive experience for your LEO players and criminals alike. We have formed this police job with many features requested from our community members such as resisting arrest, tackling, multiple LEO stations, animations, and much more. Our police job has received frequent updates and attention since it released more than one year ago! Wasabi Advanced Police Job is the most used and trusted police job outside the default ESX/QBCore police jobs.

View attachment 27781




Download :
You don't have permission to view content!
link update
 

ZHANGHOUMIN

Member
Joined
Jul 7, 2021
Messages
3
Reaction score
0
Points
146
[ citizen-server-impl] Failed to verify protected resource wasabi_bridge
 

juju885

Silver Elite
Joined
Aug 3, 2025
Messages
15
Reaction score
8
Points
106
Location
usa
beta 1 I'm trying to rebuild the server side, tell me the errors :)

-- server.lua
-- QBCore + ESX compatibility examples included. Adapt to your economy / database if needed.

local QBCore, ESX = nil, nil
local resourceName = GetCurrentResourceName()

-- try to get frameworks
if GetResourceState('qb-core') == 'started' then
QBCore = exports['qb-core']:GetCoreObject()
end

if GetResourceState('es_extended') == 'started' then
TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
end

-- CONFIG fallback (assumes you have a shared Config table in the resource)
local Config = Config or {}
-- Ensure data folder exists (no-op on many hosts, but good to know)
local dataFile = 'data/speedtraps.json'

-- Server state
local SpeedTraps = {}
local CuffedPlayers = {} -- keyed by serverId: {type='hard'|'soft', by=policeId, ts=os.time()}
local OfficerGPS = {} -- keyed by serverId = true (who has gps sharing enabled)
local OnlinePoliceCount = 0
local GSRPlayers = {} -- keyed by serverId = true/false
local OnDutyPlayers = {} -- keyed by serverId = {job=..,grade=..}
local WeaponLicenses = {} -- basic server-side table: WeaponLicenses[targetId] = true

-- -------------------------
-- Persistence helpers
-- -------------------------
local function loadSpeedTraps()
local raw = LoadResourceFile(resourceName, dataFile)
if raw and raw ~= '' then
local ok, decoded = pcall(json.decode, raw)
if ok and type(decoded) == 'table' then
SpeedTraps = decoded
print(("[wasabi_police] Loaded %d speedtraps"):format(#SpeedTraps))
return
end
end
SpeedTraps = {}
end

local function saveSpeedTraps()
local ok, err = pcall(function()
SaveResourceFile(resourceName, dataFile, json.encode(SpeedTraps, { indent = false }), -1)
end)
if not ok then
print("[wasabi_police] Error saving speedtraps:", err or "unknown")
end
end

-- -------------------------
-- Utility framework-agnostic helpers
-- -------------------------
local function isPlayerPolice(serverId)
-- returns (isPolice, jobName, jobGrade)
serverId = tonumber(serverId)
if not serverId then return false end

if QBCore then
local Player = QBCore.Functions.GetPlayer(serverId)
if Player and Player.PlayerData and Player.PlayerData.job then
local job = Player.PlayerData.job.name
local grade = Player.PlayerData.job.grade and Player.PlayerData.job.grade.level or Player.PlayerData.job.grade
if type(Config.policeJobs) == 'table' then
for _, j in ipairs(Config.policeJobs) do
if job == j or job == ('off' .. j) then return true, job, grade end
end
end
return false, job, grade
end
elseif ESX then
local xPlayer = ESX.GetPlayerFromId(serverId)
if xPlayer and xPlayer.job then
local job = xPlayer.job.name
local grade = xPlayer.job.grade
if type(Config.policeJobs) == 'table' then
for _, j in ipairs(Config.policeJobs) do
if job == j or job == ('off' .. j) then return true, job, grade end
end
end
return false, job, grade
end
else
-- fallback: if Config.policeJobs contains a string equal to "admin" or "dev", you can adapt
return false
end
return false
end

local function updateCopCount()
local players = GetPlayers()
local count = 0
for _, sid in ipairs(players) do
local isCop = select(1, isPlayerPolice(tonumber(sid)))
if isCop then count = count + 1 end
end
OnlinePoliceCount = count
TriggerClientEvent('police:SetCopCount', -1, OnlinePoliceCount)
end

local function syncOfficerGPS()
-- Send full OfficerGPS list to all police clients (so they can create blips)
TriggerClientEvent('wasabi_police:syncOfficerGPS', -1, OfficerGPS)
end

-- -------------------------
-- Callbacks (QBCore) & Generic server-side handlers
-- -------------------------
if QBCore then
-- getPlayerData: client passes list { {id = serverId}, ... } -> server returns { {id = id, name = name}, ...}
QBCore.Functions.CreateCallback('wasabi_police:getPlayerData', function(source, cb, playerList)
local out = {}
for _, v in ipairs(playerList or {}) do
local id = tonumber(v.id)
if id then
local p = QBCore.Functions.GetPlayer(id)
if p then
local name = (p.PlayerData and (p.PlayerData.charinfo and (p.PlayerData.charinfo.firstname .. ' ' .. p.PlayerData.charinfo.lastname)))
or p.PlayerData and (p.PlayerData.firstname and (p.PlayerData.firstname .. ' ' .. p.PlayerData.lastname))
or GetPlayerName(id)
out[#out + 1] = { id = id, name = name }
else
out[#out + 1] = { id = id, name = GetPlayerName(id) or ("Player " .. id) }
end
end
end
cb(out)
end)

QBCore.Functions.CreateCallback('wasabi_police:itemCheck', function(source, cb, item)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then cb(0); return end
local invItem = nil
if Player.Functions and Player.Functions.GetItemByName then
invItem = Player.Functions.GetItemByName(item)
if invItem then cb(invItem.amount or invItem.count or 0); return end
end
-- fallback attempts
if Player.PlayerData and Player.PlayerData.items then
for _, it in ipairs(Player.PlayerData.items) do
if it.name == item then cb(it.amount or it.count or 0); return end
end
end
cb(0)
end)

QBCore.Functions.CreateCallback('wasabi_police:isCuffed', function(source, cb, targetId)
targetId = tonumber(targetId)
cb(targetId and CuffedPlayers[targetId] ~= nil or false)
end)

QBCore.Functions.CreateCallback('wasabi_police:addSpeedTrap', function(source, cb, objectCoords, objHeading, configIndex, input)
local isCop = select(1, isPlayerPolice(source))
if not isCop then cb(false); return end

-- validate input
if not input or not input[1] or not tonumber(input[2]) then cb(false); return end
local id = (#SpeedTraps) + 1
local trap = {
id = id,
coords = objectCoords,
heading = objHeading,
option = configIndex,
name = input[1],
speed = tonumber(input[2]),
creator = source,
createdAt = os.time()
}
SpeedTraps[#SpeedTraps + 1] = trap
saveSpeedTraps()
-- notify all clients to add it
TriggerClientEvent('wasabi_police:addNewSpeedTrap', -1, trap)
cb(true)
end)

QBCore.Functions.CreateCallback('wasabi_police:canPurchase', function(source, cb, data)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then cb(false); return end

-- Example logic: check cash/bank
local price = tonumber(data.price) or tonumber(data.price) or 0
-- If you want purchase to come from society account, implement here (society account check)
if price <= 0 then cb(true); return end

-- Try bank first
local bank = (Player.PlayerData and Player.PlayerData.money and Player.PlayerData.money.bank) or 0
if bank >= price then
-- remove money
Player.Functions.RemoveMoney('bank', price, 'police-armoury-purchase')
cb(true); return
end

-- fallback: cash
local cash = (Player.PlayerData and Player.PlayerData.money and Player.PlayerData.money.cash) or 0
if cash >= price then
Player.Functions.RemoveMoney('cash', price, 'police-armoury-purchase')
cb(true); return
end

cb(false)
end)
end

-- Generic server-side RPC fallback (some frameworks use custom awaitServerCallback bridges)
RegisterNetEvent('wasabi_police:rpc_getPlayerData', function(playerList)
local src = source
local out = {}
for _, v in ipairs(playerList or {}) do
local id = tonumber(v.id)
if id then
local name = GetPlayerName(id) or ('Player ' .. id)
out[#out + 1] = { id = id, name = name }
end
end
TriggerClientEvent('wasabi_police:rpc_getPlayerData:reply', src, out)
end)

-- -------------------------
-- Server events (called from client)
-- -------------------------
RegisterNetEvent('wasabi_police:updateCopCount')
AddEventHandler('wasabi_police:updateCopCount', function()
updateCopCount()
end)

RegisterNetEvent('wasabi_police:addOfficerToGPS')
AddEventHandler('wasabi_police:addOfficerToGPS', function()
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
OfficerGPS[src] = true
syncOfficerGPS()
end)

-- A player can call to stop sharing GPS
RegisterNetEvent('wasabi_police:removeOfficerFromGPS')
AddEventHandler('wasabi_police:removeOfficerFromGPS', function()
local src = source
OfficerGPS[src] = nil
syncOfficerGPS()
end)

RegisterNetEvent('wasabi_police:getPoliceOnline')
AddEventHandler('wasabi_police:getPoliceOnline', function()
local src = source
TriggerClientEvent('police:SetCopCount', src, OnlinePoliceCount)
end)

RegisterNetEvent('wasabi_police:removeSpeedTrap')
AddEventHandler('wasabi_police:removeSpeedTrap', function(id)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
id = tonumber(id)
if not id then return end
for i, v in ipairs(SpeedTraps) do
if v.id == id then
table.remove(SpeedTraps, i)
saveSpeedTraps()
TriggerClientEvent('wasabi_police:removeSpeedTrap', -1, id)
return
end
end
end)

RegisterNetEvent('wasabi_police:renameSpeedTrap')
AddEventHandler('wasabi_police:renameSpeedTrap', function(id, name)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
id = tonumber(id)
if not id then return end
for i, v in ipairs(SpeedTraps) do
if v.id == id then
v.name = tostring(name)
saveSpeedTraps()
TriggerClientEvent('wasabi_police:updateSpeedTrapName', -1, id, v.name)
return
end
end
end)

RegisterNetEvent('wasabi_police:setGSR')
AddEventHandler('wasabi_police:setGSR', function(state)
local src = source
state = state and true or false
if state then
GSRPlayers[src] = { positive = true, ts = os.time() }
else
GSRPlayers[src] = nil
end
-- notify cops optionally
TriggerClientEvent('wasabi_police:gsrUpdate', -1, { id = src, positive = state })
end)

RegisterNetEvent('wasabi_police:lockpickHandcuffs')
AddEventHandler('wasabi_police:lockpickHandcuffs', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
-- lockpicking could be done by anyone if allowed; adapt to config
if not targetServerId then return end
targetServerId = tonumber(targetServerId)
-- If target is cuffed, uncuff them
if CuffedPlayers[targetServerId] then
CuffedPlayers[targetServerId] = nil
-- notify target to uncuff themselves
TriggerClientEvent('wasabi_police:uncuff', targetServerId)
-- play animation on the lockpicker (client side should handle 'uncuffAnim' if needed)
TriggerClientEvent('wasabi_police:uncuffAnim', src, targetServerId)
end
end)

RegisterNetEvent('wasabi_police:breakLockpick')
AddEventHandler('wasabi_police:breakLockpick', function()
local src = source
-- remove one lockpick from source (QBCore / ESX examples)
if QBCore then
local Player = QBCore.Functions.GetPlayer(src)
if Player and Player.Functions then
Player.Functions.RemoveItem('lockpick', 1)
TriggerClientEvent('wasabi_bridge:notify', src, "Lockpick cassé", "Votre casse-outil s'est brisé.", 'error')
end
elseif ESX then
local xPlayer = ESX.GetPlayerFromId(src)
if xPlayer then
xPlayer.removeInventoryItem('lockpick', 1)
TriggerClientEvent('wasabi_bridge:notify', src, "Lockpick cassé", "Votre casse-outil s'est brisé.", 'error')
end
end
end)

-- put someone in vehicle: officer calls server with target id => server asks target client to put themselves in vehicle
RegisterNetEvent('wasabi_police:inVehiclePlayer')
AddEventHandler('wasabi_police:inVehiclePlayer', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end
TriggerClientEvent('wasabi_police:putInVehicle', targetServerId)
end)

-- take someone out of vehicle
RegisterNetEvent('wasabi_police:eek:utVehiclePlayer')
AddEventHandler('wasabi_police:eek:utVehiclePlayer', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end
-- client expects policeID param
TriggerClientEvent('wasabi_police:takeFromVehicle', targetServerId, src)
end)

-- seizure of cash (simplified): remove cash from target and give to officer or society
RegisterNetEvent('wasabi_police:seizeCash')
AddEventHandler('wasabi_police:seizeCash', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end

-- Example: transfer all cash from target to officer (or society). Adapt to your economy.
if QBCore then
local target = QBCore.Functions.GetPlayer(targetServerId)
local officer = QBCore.Functions.GetPlayer(src)
if target and officer then
local cash = (target.PlayerData.money.cash or 0)
if cash and cash > 0 then
target.Functions.RemoveMoney('cash', cash, 'seized-by-police')
officer.Functions.AddMoney('cash', cash, 'seized-from-player')
TriggerClientEvent('wasabi_bridge:notify', src, "Argent saisi", ("Vous avez saisi $%s"):format(cash), 'success')
TriggerClientEvent('wasabi_bridge:notify', targetServerId, "Argent saisi", "La police vous a saisi de l'argent.", 'error')
else
TriggerClientEvent('wasabi_bridge:notify', src, "Rien à saisir", "Le joueur n'a pas d'argent liquide.", 'error')
end
end
elseif ESX then
local tx = ESX.GetPlayerFromId(targetServerId)
local ox = ESX.GetPlayerFromId(src)
if tx and ox then
local cash = tx.getMoney()
if cash and cash > 0 then
tx.removeMoney(cash)
ox.addMoney(cash)
TriggerClientEvent('wasabi_bridge:notify', src, "Argent saisi", ("Vous avez saisi $%s"):format(cash), 'success')
TriggerClientEvent('wasabi_bridge:notify', targetServerId, "Argent saisi", "La police vous a saisi de l'argent.", 'error')
else
TriggerClientEvent('wasabi_bridge:notify', src, "Rien à saisir", "Le joueur n'a pas d'argent liquide.", 'error')
end
end
else
TriggerClientEvent('wasabi_bridge:notify', src, "Seize unavailable", "No economy integration present.", 'error')
end
end)

-- Escort / release
RegisterNetEvent('wasabi_police:releasePlayer')
AddEventHandler('wasabi_police:releasePlayer', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
-- It may be called by the escorting officer or client on death; allow if officer (or optional: allow any)
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end

-- Tell the escorted target to free themselves
TriggerClientEvent('wasabi_police:releasePlayerFromEscort', targetServerId, src)
-- Tell the officer to stop escorting (client will verify target id)
TriggerClientEvent('wasabi_police:stopEscorting', src, targetServerId)
end)

RegisterNetEvent('wasabi_police:escortPlayerStop')
AddEventHandler('wasabi_police:escortPlayerStop', function(targetServerId, forced)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end

TriggerClientEvent('wasabi_police:releasePlayerFromEscort', targetServerId, src)
TriggerClientEvent('wasabi_police:stopEscorting', src, targetServerId)
end)

-- toggle duty (server side)
RegisterNetEvent('wasabi_police:svToggleDuty')
AddEventHandler('wasabi_police:svToggleDuty', function(jobName, grade)
local src = source
if not jobName then return end
-- server stores on-duty map and triggers cop-count update
if OnDutyPlayers[src] then
-- remove
OnDutyPlayers[src] = nil
OfficerGPS[src] = nil
else
OnDutyPlayers[src] = { job = jobName, grade = grade }
-- optionally auto add to GPS if config
if Config.GPSBlips and Config.GPSBlips.enabled and not Config.GPSBlips.item then
OfficerGPS[src] = true
end
end
updateCopCount()
syncOfficerGPS()
end)

-- Weapon license management (basic)
RegisterNetEvent('wasabi_police:weaponLicense')
AddEventHandler('wasabi_police:weaponLicense', function(targetId)
local src = source
local isCop, jobName, grade = select(1, isPlayerPolice(src))
if not isCop then return end
-- permission check: grade min
local minGrade = (Config.GrantWeaponLicenses and Config.GrantWeaponLicenses.minGrade) or 0
if tonumber(grade) < tonumber(minGrade) then
TriggerClientEvent('wasabi_bridge:notify', src, "Grade trop bas", "Vous n'avez pas la permission.", 'error')
return
end
targetId = tonumber(targetId)
if not targetId then return end

-- Store license server-side (for demo). Integrate DB here as needed.
WeaponLicenses[targetId] = WeaponLicenses[targetId] or {}
WeaponLicenses[targetId].weapon = true
-- optionally persist to file or database
TriggerClientEvent('wasabi_bridge:notify', src, "Licence accordée", ("Vous avez accordé une licence à %s"):format(targetId), 'success')
TriggerClientEvent('wasabi_bridge:notify', targetId, "Licence reçue", "Vous avez reçu une licence d'arme.", 'success')
end)

RegisterNetEvent('wasabi_police:revokeWeaponLicense')
AddEventHandler('wasabi_police:revokeWeaponLicense', function(targetId)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetId = tonumber(targetId)
if not targetId then return end
if WeaponLicenses[targetId] then WeaponLicenses[targetId] = nil end
TriggerClientEvent('wasabi_bridge:notify', src, "Licence retirée", ("Licence retirée pour %s"):format(targetId), 'success')
TriggerClientEvent('wasabi_bridge:notify', targetId, "Licence retirée", "Votre licence d'arme a été retirée.", 'error')
end)

-- When a client asks for initial SpeedTraps, send them
RegisterNetEvent('wasabi_police:requestInitSpeedTraps')
AddEventHandler('wasabi_police:requestInitSpeedTraps', function()
local src = source
TriggerClientEvent('wasabi_police:initSpeedTraps', src, SpeedTraps)
end)

-- -------------------------
-- Resource / player lifecycle
-- -------------------------
AddEventHandler('playerDropped', function(reason)
local src = source
-- cleanup gps/duty/cuffed
OfficerGPS[src] = nil
OnDutyPlayers[src] = nil
CuffedPlayers[src] = nil
GSRPlayers[src] = nil
updateCopCount()
syncOfficerGPS()
end)

AddEventHandler('onResourceStart', function(resName)
if resName ~= resourceName then return end
loadSpeedTraps()
-- broadcast initial list to connected players
Citizen.SetTimeout(1500, function()
local players = GetPlayers()
for _, p in ipairs(players) do
TriggerClientEvent('wasabi_police:initSpeedTraps', p, SpeedTraps)
TriggerClientEvent('police:SetCopCount', p, OnlinePoliceCount)
end
end)
end)

-- On server start load speedtraps immediately
loadSpeedTraps()
Citizen.CreateThread(function() updateCopCount() end)

-- -------------------------
-- Convenience exports (optional)
-- -------------------------
exports('GetSpeedTraps', function()
return SpeedTraps
end)

exports('IsPlayerCuffed', function(serverId)
return CuffedPlayers[tonumber(serverId)] ~= nil
end)

-- -------------------------
-- Notes for integrators:
-- - adapt economy interactions (seizeCash, canPurchase) to your server's rules (society account, evidence stashes, etc.)
-- - persist WeaponLicenses if you need permanence (database)
-- - you can extend OfficerGPS to store player's coords and broadcast smaller packets
-- -------------------------
print(("[wasabi_police] Server ready (%s). QBCore=%s ESX=%s"):format(resourceName, tostring(QBCore ~= nil), tostring(ESX ~= nil)))
 

Attachments

  • server.txt
    21.5 KB · Views: 139
Last edited:

juju885

Silver Elite
Joined
Aug 3, 2025
Messages
15
Reaction score
8
Points
106
Location
usa
beta 1 I'm trying to rebuild the server side, tell me the errors :)
-- server.lua
-- QBCore + ESX compatibility examples included. Adapt to your economy / database if needed.

local QBCore, ESX = nil, nil
local resourceName = GetCurrentResourceName()

-- try to get frameworks
if GetResourceState('qb-core') == 'started' then
QBCore = exports['qb-core']:GetCoreObject()
end

if GetResourceState('es_extended') == 'started' then
TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
end

-- CONFIG fallback (assumes you have a shared Config table in the resource)
local Config = Config or {}
-- Ensure data folder exists (no-op on many hosts, but good to know)
local dataFile = 'data/speedtraps.json'

-- Server state
local SpeedTraps = {}
local CuffedPlayers = {} -- keyed by serverId: {type='hard'|'soft', by=policeId, ts=os.time()}
local OfficerGPS = {} -- keyed by serverId = true (who has gps sharing enabled)
local OnlinePoliceCount = 0
local GSRPlayers = {} -- keyed by serverId = true/false
local OnDutyPlayers = {} -- keyed by serverId = {job=..,grade=..}
local WeaponLicenses = {} -- basic server-side table: WeaponLicenses[targetId] = true

-- -------------------------
-- Persistence helpers
-- -------------------------
local function loadSpeedTraps()
local raw = LoadResourceFile(resourceName, dataFile)
if raw and raw ~= '' then
local ok, decoded = pcall(json.decode, raw)
if ok and type(decoded) == 'table' then
SpeedTraps = decoded
print(("[wasabi_police] Loaded %d speedtraps"):format(#SpeedTraps))
return
end
end
SpeedTraps = {}
end

local function saveSpeedTraps()
local ok, err = pcall(function()
SaveResourceFile(resourceName, dataFile, json.encode(SpeedTraps, { indent = false }), -1)
end)
if not ok then
print("[wasabi_police] Error saving speedtraps:", err or "unknown")
end
end

-- -------------------------
-- Utility framework-agnostic helpers
-- -------------------------
local function isPlayerPolice(serverId)
-- returns (isPolice, jobName, jobGrade)
serverId = tonumber(serverId)
if not serverId then return false end

if QBCore then
local Player = QBCore.Functions.GetPlayer(serverId)
if Player and Player.PlayerData and Player.PlayerData.job then
local job = Player.PlayerData.job.name
local grade = Player.PlayerData.job.grade and Player.PlayerData.job.grade.level or Player.PlayerData.job.grade
if type(Config.policeJobs) == 'table' then
for _, j in ipairs(Config.policeJobs) do
if job == j or job == ('off' .. j) then return true, job, grade end
end
end
return false, job, grade
end
elseif ESX then
local xPlayer = ESX.GetPlayerFromId(serverId)
if xPlayer and xPlayer.job then
local job = xPlayer.job.name
local grade = xPlayer.job.grade
if type(Config.policeJobs) == 'table' then
for _, j in ipairs(Config.policeJobs) do
if job == j or job == ('off' .. j) then return true, job, grade end
end
end
return false, job, grade
end
else
-- fallback: if Config.policeJobs contains a string equal to "admin" or "dev", you can adapt
return false
end
return false
end

local function updateCopCount()
local players = GetPlayers()
local count = 0
for _, sid in ipairs(players) do
local isCop = select(1, isPlayerPolice(tonumber(sid)))
if isCop then count = count + 1 end
end
OnlinePoliceCount = count
TriggerClientEvent('police:SetCopCount', -1, OnlinePoliceCount)
end

local function syncOfficerGPS()
-- Send full OfficerGPS list to all police clients (so they can create blips)
TriggerClientEvent('wasabi_police:syncOfficerGPS', -1, OfficerGPS)
end

-- -------------------------
-- Callbacks (QBCore) & Generic server-side handlers
-- -------------------------
if QBCore then
-- getPlayerData: client passes list { {id = serverId}, ... } -> server returns { {id = id, name = name}, ...}
QBCore.Functions.CreateCallback('wasabi_police:getPlayerData', function(source, cb, playerList)
local out = {}
for _, v in ipairs(playerList or {}) do
local id = tonumber(v.id)
if id then
local p = QBCore.Functions.GetPlayer(id)
if p then
local name = (p.PlayerData and (p.PlayerData.charinfo and (p.PlayerData.charinfo.firstname .. ' ' .. p.PlayerData.charinfo.lastname)))
or p.PlayerData and (p.PlayerData.firstname and (p.PlayerData.firstname .. ' ' .. p.PlayerData.lastname))
or GetPlayerName(id)
out[#out + 1] = { id = id, name = name }
else
out[#out + 1] = { id = id, name = GetPlayerName(id) or ("Player " .. id) }
end
end
end
cb(out)
end)

QBCore.Functions.CreateCallback('wasabi_police:itemCheck', function(source, cb, item)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then cb(0); return end
local invItem = nil
if Player.Functions and Player.Functions.GetItemByName then
invItem = Player.Functions.GetItemByName(item)
if invItem then cb(invItem.amount or invItem.count or 0); return end
end
-- fallback attempts
if Player.PlayerData and Player.PlayerData.items then
for _, it in ipairs(Player.PlayerData.items) do
if it.name == item then cb(it.amount or it.count or 0); return end
end
end
cb(0)
end)

QBCore.Functions.CreateCallback('wasabi_police:isCuffed', function(source, cb, targetId)
targetId = tonumber(targetId)
cb(targetId and CuffedPlayers[targetId] ~= nil or false)
end)

QBCore.Functions.CreateCallback('wasabi_police:addSpeedTrap', function(source, cb, objectCoords, objHeading, configIndex, input)
local isCop = select(1, isPlayerPolice(source))
if not isCop then cb(false); return end

-- validate input
if not input or not input[1] or not tonumber(input[2]) then cb(false); return end
local id = (#SpeedTraps) + 1
local trap = {
id = id,
coords = objectCoords,
heading = objHeading,
option = configIndex,
name = input[1],
speed = tonumber(input[2]),
creator = source,
createdAt = os.time()
}
SpeedTraps[#SpeedTraps + 1] = trap
saveSpeedTraps()
-- notify all clients to add it
TriggerClientEvent('wasabi_police:addNewSpeedTrap', -1, trap)
cb(true)
end)

QBCore.Functions.CreateCallback('wasabi_police:canPurchase', function(source, cb, data)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then cb(false); return end

-- Example logic: check cash/bank
local price = tonumber(data.price) or tonumber(data.price) or 0
-- If you want purchase to come from society account, implement here (society account check)
if price <= 0 then cb(true); return end

-- Try bank first
local bank = (Player.PlayerData and Player.PlayerData.money and Player.PlayerData.money.bank) or 0
if bank >= price then
-- remove money
Player.Functions.RemoveMoney('bank', price, 'police-armoury-purchase')
cb(true); return
end

-- fallback: cash
local cash = (Player.PlayerData and Player.PlayerData.money and Player.PlayerData.money.cash) or 0
if cash >= price then
Player.Functions.RemoveMoney('cash', price, 'police-armoury-purchase')
cb(true); return
end

cb(false)
end)
end

-- Generic server-side RPC fallback (some frameworks use custom awaitServerCallback bridges)
RegisterNetEvent('wasabi_police:rpc_getPlayerData', function(playerList)
local src = source
local out = {}
for _, v in ipairs(playerList or {}) do
local id = tonumber(v.id)
if id then
local name = GetPlayerName(id) or ('Player ' .. id)
out[#out + 1] = { id = id, name = name }
end
end
TriggerClientEvent('wasabi_police:rpc_getPlayerData:reply', src, out)
end)

-- -------------------------
-- Server events (called from client)
-- -------------------------
RegisterNetEvent('wasabi_police:updateCopCount')
AddEventHandler('wasabi_police:updateCopCount', function()
updateCopCount()
end)

RegisterNetEvent('wasabi_police:addOfficerToGPS')
AddEventHandler('wasabi_police:addOfficerToGPS', function()
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
OfficerGPS[src] = true
syncOfficerGPS()
end)

-- A player can call to stop sharing GPS
RegisterNetEvent('wasabi_police:removeOfficerFromGPS')
AddEventHandler('wasabi_police:removeOfficerFromGPS', function()
local src = source
OfficerGPS[src] = nil
syncOfficerGPS()
end)

RegisterNetEvent('wasabi_police:getPoliceOnline')
AddEventHandler('wasabi_police:getPoliceOnline', function()
local src = source
TriggerClientEvent('police:SetCopCount', src, OnlinePoliceCount)
end)

RegisterNetEvent('wasabi_police:removeSpeedTrap')
AddEventHandler('wasabi_police:removeSpeedTrap', function(id)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
id = tonumber(id)
if not id then return end
for i, v in ipairs(SpeedTraps) do
if v.id == id then
table.remove(SpeedTraps, i)
saveSpeedTraps()
TriggerClientEvent('wasabi_police:removeSpeedTrap', -1, id)
return
end
end
end)

RegisterNetEvent('wasabi_police:renameSpeedTrap')
AddEventHandler('wasabi_police:renameSpeedTrap', function(id, name)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
id = tonumber(id)
if not id then return end
for i, v in ipairs(SpeedTraps) do
if v.id == id then
v.name = tostring(name)
saveSpeedTraps()
TriggerClientEvent('wasabi_police:updateSpeedTrapName', -1, id, v.name)
return
end
end
end)

RegisterNetEvent('wasabi_police:setGSR')
AddEventHandler('wasabi_police:setGSR', function(state)
local src = source
state = state and true or false
if state then
GSRPlayers[src] = { positive = true, ts = os.time() }
else
GSRPlayers[src] = nil
end
-- notify cops optionally
TriggerClientEvent('wasabi_police:gsrUpdate', -1, { id = src, positive = state })
end)

RegisterNetEvent('wasabi_police:lockpickHandcuffs')
AddEventHandler('wasabi_police:lockpickHandcuffs', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
-- lockpicking could be done by anyone if allowed; adapt to config
if not targetServerId then return end
targetServerId = tonumber(targetServerId)
-- If target is cuffed, uncuff them
if CuffedPlayers[targetServerId] then
CuffedPlayers[targetServerId] = nil
-- notify target to uncuff themselves
TriggerClientEvent('wasabi_police:uncuff', targetServerId)
-- play animation on the lockpicker (client side should handle 'uncuffAnim' if needed)
TriggerClientEvent('wasabi_police:uncuffAnim', src, targetServerId)
end
end)

RegisterNetEvent('wasabi_police:breakLockpick')
AddEventHandler('wasabi_police:breakLockpick', function()
local src = source
-- remove one lockpick from source (QBCore / ESX examples)
if QBCore then
local Player = QBCore.Functions.GetPlayer(src)
if Player and Player.Functions then
Player.Functions.RemoveItem('lockpick', 1)
TriggerClientEvent('wasabi_bridge:notify', src, "Lockpick cassé", "Votre casse-outil s'est brisé.", 'error')
end
elseif ESX then
local xPlayer = ESX.GetPlayerFromId(src)
if xPlayer then
xPlayer.removeInventoryItem('lockpick', 1)
TriggerClientEvent('wasabi_bridge:notify', src, "Lockpick cassé", "Votre casse-outil s'est brisé.", 'error')
end
end
end)

-- put someone in vehicle: officer calls server with target id => server asks target client to put themselves in vehicle
RegisterNetEvent('wasabi_police:inVehiclePlayer')
AddEventHandler('wasabi_police:inVehiclePlayer', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end
TriggerClientEvent('wasabi_police:putInVehicle', targetServerId)
end)

-- take someone out of vehicle
RegisterNetEvent('wasabi_police:eek:utVehiclePlayer')
AddEventHandler('wasabi_police:eek:utVehiclePlayer', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end
-- client expects policeID param
TriggerClientEvent('wasabi_police:takeFromVehicle', targetServerId, src)
end)

-- seizure of cash (simplified): remove cash from target and give to officer or society
RegisterNetEvent('wasabi_police:seizeCash')
AddEventHandler('wasabi_police:seizeCash', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end

-- Example: transfer all cash from target to officer (or society). Adapt to your economy.
if QBCore then
local target = QBCore.Functions.GetPlayer(targetServerId)
local officer = QBCore.Functions.GetPlayer(src)
if target and officer then
local cash = (target.PlayerData.money.cash or 0)
if cash and cash > 0 then
target.Functions.RemoveMoney('cash', cash, 'seized-by-police')
officer.Functions.AddMoney('cash', cash, 'seized-from-player')
TriggerClientEvent('wasabi_bridge:notify', src, "Argent saisi", ("Vous avez saisi $%s"):format(cash), 'success')
TriggerClientEvent('wasabi_bridge:notify', targetServerId, "Argent saisi", "La police vous a saisi de l'argent.", 'error')
else
TriggerClientEvent('wasabi_bridge:notify', src, "Rien à saisir", "Le joueur n'a pas d'argent liquide.", 'error')
end
end
elseif ESX then
local tx = ESX.GetPlayerFromId(targetServerId)
local ox = ESX.GetPlayerFromId(src)
if tx and ox then
local cash = tx.getMoney()
if cash and cash > 0 then
tx.removeMoney(cash)
ox.addMoney(cash)
TriggerClientEvent('wasabi_bridge:notify', src, "Argent saisi", ("Vous avez saisi $%s"):format(cash), 'success')
TriggerClientEvent('wasabi_bridge:notify', targetServerId, "Argent saisi", "La police vous a saisi de l'argent.", 'error')
else
TriggerClientEvent('wasabi_bridge:notify', src, "Rien à saisir", "Le joueur n'a pas d'argent liquide.", 'error')
end
end
else
TriggerClientEvent('wasabi_bridge:notify', src, "Seize unavailable", "No economy integration present.", 'error')
end
end)

-- Escort / release
RegisterNetEvent('wasabi_police:releasePlayer')
AddEventHandler('wasabi_police:releasePlayer', function(targetServerId)
local src = source
local isCop = select(1, isPlayerPolice(src))
-- It may be called by the escorting officer or client on death; allow if officer (or optional: allow any)
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end

-- Tell the escorted target to free themselves
TriggerClientEvent('wasabi_police:releasePlayerFromEscort', targetServerId, src)
-- Tell the officer to stop escorting (client will verify target id)
TriggerClientEvent('wasabi_police:stopEscorting', src, targetServerId)
end)

RegisterNetEvent('wasabi_police:escortPlayerStop')
AddEventHandler('wasabi_police:escortPlayerStop', function(targetServerId, forced)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetServerId = tonumber(targetServerId)
if not targetServerId then return end

TriggerClientEvent('wasabi_police:releasePlayerFromEscort', targetServerId, src)
TriggerClientEvent('wasabi_police:stopEscorting', src, targetServerId)
end)

-- toggle duty (server side)
RegisterNetEvent('wasabi_police:svToggleDuty')
AddEventHandler('wasabi_police:svToggleDuty', function(jobName, grade)
local src = source
if not jobName then return end
-- server stores on-duty map and triggers cop-count update
if OnDutyPlayers[src] then
-- remove
OnDutyPlayers[src] = nil
OfficerGPS[src] = nil
else
OnDutyPlayers[src] = { job = jobName, grade = grade }
-- optionally auto add to GPS if config
if Config.GPSBlips and Config.GPSBlips.enabled and not Config.GPSBlips.item then
OfficerGPS[src] = true
end
end
updateCopCount()
syncOfficerGPS()
end)

-- Weapon license management (basic)
RegisterNetEvent('wasabi_police:weaponLicense')
AddEventHandler('wasabi_police:weaponLicense', function(targetId)
local src = source
local isCop, jobName, grade = select(1, isPlayerPolice(src))
if not isCop then return end
-- permission check: grade min
local minGrade = (Config.GrantWeaponLicenses and Config.GrantWeaponLicenses.minGrade) or 0
if tonumber(grade) < tonumber(minGrade) then
TriggerClientEvent('wasabi_bridge:notify', src, "Grade trop bas", "Vous n'avez pas la permission.", 'error')
return
end
targetId = tonumber(targetId)
if not targetId then return end

-- Store license server-side (for demo). Integrate DB here as needed.
WeaponLicenses[targetId] = WeaponLicenses[targetId] or {}
WeaponLicenses[targetId].weapon = true
-- optionally persist to file or database
TriggerClientEvent('wasabi_bridge:notify', src, "Licence accordée", ("Vous avez accordé une licence à %s"):format(targetId), 'success')
TriggerClientEvent('wasabi_bridge:notify', targetId, "Licence reçue", "Vous avez reçu une licence d'arme.", 'success')
end)

RegisterNetEvent('wasabi_police:revokeWeaponLicense')
AddEventHandler('wasabi_police:revokeWeaponLicense', function(targetId)
local src = source
local isCop = select(1, isPlayerPolice(src))
if not isCop then return end
targetId = tonumber(targetId)
if not targetId then return end
if WeaponLicenses[targetId] then WeaponLicenses[targetId] = nil end
TriggerClientEvent('wasabi_bridge:notify', src, "Licence retirée", ("Licence retirée pour %s"):format(targetId), 'success')
TriggerClientEvent('wasabi_bridge:notify', targetId, "Licence retirée", "Votre licence d'arme a été retirée.", 'error')
end)

-- When a client asks for initial SpeedTraps, send them
RegisterNetEvent('wasabi_police:requestInitSpeedTraps')
AddEventHandler('wasabi_police:requestInitSpeedTraps', function()
local src = source
TriggerClientEvent('wasabi_police:initSpeedTraps', src, SpeedTraps)
end)

-- -------------------------
-- Resource / player lifecycle
-- -------------------------
AddEventHandler('playerDropped', function(reason)
local src = source
-- cleanup gps/duty/cuffed
OfficerGPS[src] = nil
OnDutyPlayers[src] = nil
CuffedPlayers[src] = nil
GSRPlayers[src] = nil
updateCopCount()
syncOfficerGPS()
end)

AddEventHandler('onResourceStart', function(resName)
if resName ~= resourceName then return end
loadSpeedTraps()
-- broadcast initial list to connected players
Citizen.SetTimeout(1500, function()
local players = GetPlayers()
for _, p in ipairs(players) do
TriggerClientEvent('wasabi_police:initSpeedTraps', p, SpeedTraps)
TriggerClientEvent('police:SetCopCount', p, OnlinePoliceCount)
end
end)
end)

-- On server start load speedtraps immediately
loadSpeedTraps()
Citizen.CreateThread(function() updateCopCount() end)

-- -------------------------
-- Convenience exports (optional)
-- -------------------------
exports('GetSpeedTraps', function()
return SpeedTraps
end)

exports('IsPlayerCuffed', function(serverId)
return CuffedPlayers[tonumber(serverId)] ~= nil
end)

-- -------------------------
-- Notes for integrators:
-- - adapt economy interactions (seizeCash, canPurchase) to your server's rules (society account, evidence stashes, etc.)
-- - persist WeaponLicenses if you need permanence (database)
-- - you can extend OfficerGPS to store player's coords and broadcast smaller packets
-- -------------------------
print(("[wasabi_police] Server ready (%s). QBCore=%s ESX=%s"):format(resourceName, tostring(QBCore ~= nil), tostring(ESX ~= nil)))
 
Top