584 lines
19 KiB
Lua
Raw Normal View History

2025-04-07 01:41:12 +00:00
local config = require 'config.client'
local sharedConfig = require 'config.shared'
local currentGarage = 0
local inFingerprint = false
local fingerprintSessionId = nil
local inStash = false
local inTrash = false
local inHelicopter = false
local inImpound = false
local inGarage = false
local inPrompt = false
local function openFingerprintUi()
SendNUIMessage({
type = 'fingerprintOpen'
})
SetNuiFocus(true, true)
end
local function setCarItemsInfo()
local items = {}
for _, item in pairs(config.carItems) do
local itemInfo = exports.ox_inventory:Items()[item.name:lower()]
items[item.slot] = {
name = itemInfo.name,
amount = tonumber(item.amount),
info = item.info,
label = itemInfo.label,
description = itemInfo.description or '',
weight = itemInfo.weight,
type = itemInfo.type,
unique = itemInfo.unique,
useable = itemInfo.useable,
image = itemInfo.image,
slot = item.slot
}
end
config.carItems = items
end
local function doCarDamage(currentVehicle, veh)
local smash = false
local damageOutside = false
local popTires = false
local engine = veh.engine + 0.0
local body = veh.body + 0.0
if engine < 200.0 then engine = 200.0 end
if engine > 1000.0 then engine = 950.0 end
if body < 150.0 then body = 150.0 end
if body < 950.0 then smash = true end
if body < 920.0 then damageOutside = true end
if body < 920.0 then popTires = true end
Wait(100)
SetVehicleEngineHealth(currentVehicle, engine)
if smash then
for i = 0, 4 do
SmashVehicleWindow(currentVehicle, i)
end
end
if damageOutside then
SetVehicleDoorBroken(currentVehicle, 1, true)
SetVehicleDoorBroken(currentVehicle, 6, true)
SetVehicleDoorBroken(currentVehicle, 4, true)
end
if popTires then
for i = 1, 4 do
SetVehicleTyreBurst(currentVehicle, i, false, 990.0)
end
end
if body < 1000 then
SetVehicleBodyHealth(currentVehicle, 985.1)
end
end
local function takeOutImpound(vehicle)
if not inImpound then return end
local coords = sharedConfig.locations.impound[currentGarage]
if not coords then return end
local netId = lib.callback.await('qbx_policejob:server:spawnVehicle', false, vehicle.vehicle, coords, vehicle.plate, vehicle.id)
local veh = lib.waitFor(function()
if NetworkDoesEntityExistWithNetworkId(netId) then
return NetToVeh(netId)
end
end)
local properties = lib.callback.await('qb-garage:server:GetVehicleProperties', false, vehicle.plate)
lib.setVehicleProperties(veh, properties)
SetVehicleFuelLevel(veh, vehicle.fuel)
doCarDamage(veh, vehicle)
TriggerServerEvent('police:server:TakeOutImpound', vehicle.plate, currentGarage)
SetVehicleEngineOn(veh, true, true, false)
end
local function takeOutVehicle(vehicleInfo)
if not inGarage then return end
local coords = sharedConfig.locations.vehicle[currentGarage]
if not coords then return end
local pattern = ''
for _ = 1, 8 - #sharedConfig.policePlatePrefix do
pattern = pattern..'1'
end
local plate = sharedConfig.policePlatePrefix..lib.string.random(pattern):upper()
local netId = lib.callback.await('qbx_policejob:server:spawnVehicle', false, vehicleInfo, coords, plate, true)
local veh = lib.waitFor(function()
if NetworkDoesEntityExistWithNetworkId(netId) then
return NetToVeh(netId)
end
end, nil, sharedConfig.timeout)
assert(veh ~= 0, 'Something went wrong spawning the vehicle')
setCarItemsInfo()
SetEntityHeading(veh, coords.w)
SetVehicleFuelLevel(veh, 100.0)
if config.vehicleSettings[vehicleInfo] then
if config.vehicleSettings[vehicleInfo].extras then
qbx.setVehicleExtras(veh, config.vehicleSettings[vehicleInfo].extras)
end
if config.vehicleSettings[vehicleInfo].livery then
SetVehicleLivery(veh, config.vehicleSettings[vehicleInfo].livery)
end
end
SetVehicleEngineOn(veh, true, true, false)
end
local function addGarageMenuItems(destinationOptions, sourceOptions)
for veh, label in pairs(sourceOptions) do
destinationOptions[#destinationOptions + 1] = {
title = label,
onSelect = function()
takeOutVehicle(veh)
end,
}
end
return destinationOptions
end
local function openGarageMenu()
local authorizedVehicles = config.authorizedVehicles[QBX.PlayerData.job.grade.level]
local options = {}
options = addGarageMenuItems(options, authorizedVehicles)
options = addGarageMenuItems(options, config.whitelistedVehicles)
lib.registerContext({
id = 'vehicleMenu',
title = locale('menu.garage_title'),
options = options,
})
lib.showContext('vehicleMenu')
end
local function openImpoundMenu()
local options = {}
local result = lib.callback.await('police:GetImpoundedVehicles', false)
if not result then
exports.qbx_core:Notify(locale('error.no_impound'), 'error')
else
local vehicles = exports.qbx_core:GetVehiclesByName()
for _, v in pairs(result) do
local enginePercent = qbx.math.round(v.engine / 10, 0)
local currentFuel = v.fuel
local vName = vehicles[v.vehicle].name
options[#options + 1] = {
title = vName..' ['..v.plate..']',
onSelect = function()
takeOutImpound(v)
end,
metadata = {
{label = 'Engine', value = enginePercent .. ' %'},
{label = 'Fuel', value = currentFuel .. ' %'}
},
}
end
end
lib.registerContext({
id = 'impoundMenu',
title = locale('menu.impound'),
options = options
})
lib.showContext('impoundMenu')
end
---TODO: global evidence lockers instead of location specific
local function openEvidenceLockerSelectInput(currentEvidence)
local input = lib.inputDialog(locale('info.evidence_stash', currentEvidence), {locale('info.slot')})
if not input then return end
local slotNumber = tonumber(input[1])
TriggerServerEvent('inventory:server:OpenInventory', 'stash', locale('info.current_evidence', currentEvidence, slotNumber), {
maxweight = 4000000,
slots = 500,
})
TriggerEvent('inventory:client:SetCurrentStash', locale('info.current_evidence', currentEvidence, slotNumber))
end
local function openEvidenceMenu()
local pos = GetEntityCoords(cache.ped)
for k, v in pairs(sharedConfig.locations.evidence) do
if #(pos - v) < 1 then
openEvidenceLockerSelectInput(k)
return
end
end
end
local function spawnHelicopter()
if not inHelicopter then return end
local plyCoords = GetEntityCoords(cache.ped)
local coords = vec4(plyCoords.x, plyCoords.y, plyCoords.z, GetEntityHeading(cache.ped))
local netId = lib.callback.await('qbx_policejob:server:spawnVehicle', false, config.policeHelicopter, coords, 'ZULU'..lib.string.random('1111'), true)
local heli = lib.waitFor(function()
if NetworkDoesEntityExistWithNetworkId(netId) then
return NetToVeh(netId)
end
end, nil, sharedConfig.timeout)
SetVehicleLivery(heli , 0)
SetVehicleMod(heli, 0, 48, false)
SetEntityHeading(heli, coords.w)
SetVehicleFuelLevel(heli, 100.0)
SetVehicleEngineOn(heli, true, true, false)
end
local function scanFingerprint()
if not inFingerprint then return end
local playerId = lib.getClosestPlayer(GetEntityCoords(cache.ped), 2.5, false)
if not playerId then
return exports.qbx_core:Notify(locale('error.none_nearby'), 'error')
end
TriggerServerEvent('police:server:showFingerprint', GetPlayerServerId(playerId))
end
local function uiPrompt(promptType, id)
if QBX.PlayerData.job.type ~= 'leo' then return end
CreateThread(function()
while inPrompt do
Wait(0)
if IsControlJustReleased(0, 38) then
if promptType == 'duty' then
ToggleDuty()
lib.hideTextUI()
break
elseif promptType == 'garage' then
if not inGarage then return end
if cache.vehicle then
DeleteVehicle(cache.vehicle)
lib.hideTextUI()
break
else
openGarageMenu()
lib.hideTextUI()
break
end
elseif promptType == 'evidence' then
openEvidenceMenu()
lib.hideTextUI()
break
elseif promptType == 'impound' then
if not inImpound then return end
if cache.vehicle then
DeleteVehicle(cache.vehicle)
lib.hideTextUI()
break
else
openImpoundMenu()
lib.hideTextUI()
break
end
elseif promptType == 'heli' then
if not inHelicopter then return end
if cache.vehicle then
DeleteVehicle(cache.vehicle)
lib.hideTextUI()
break
else
spawnHelicopter()
lib.hideTextUI()
break
end
elseif promptType == 'fingerprint' then
scanFingerprint()
lib.hideTextUI()
break
elseif promptType == 'trash' then
if not inTrash then return end
exports.ox_inventory:openInventory('stash', ('policetrash_%s'):format(id))
break
elseif promptType == 'stash' then
if not inStash then return end
exports.ox_inventory:openInventory('stash', {id = 'policelocker'})
break
end
end
end
end)
end
RegisterNUICallback('closeFingerprint', function(_, cb)
SetNuiFocus(false, false)
cb('ok')
end)
RegisterNetEvent('police:client:showFingerprint', function(playerId)
openFingerprintUi()
fingerprintSessionId = playerId
end)
RegisterNetEvent('police:client:showFingerprintId', function(fid)
SendNUIMessage({
type = 'updateFingerprintId',
fingerprintId = fid
})
PlaySound(-1, 'Event_Start_Text', 'GTAO_FM_Events_Soundset', false, 0, true)
end)
RegisterNUICallback('doFingerScan', function(_, cb)
TriggerServerEvent('police:server:showFingerprintId', fingerprintSessionId)
cb('ok')
end)
RegisterNetEvent('police:client:SendEmergencyMessage', function(coords, message)
TriggerServerEvent('police:server:SendEmergencyMessage', coords, message)
TriggerEvent('police:client:CallAnim')
end)
RegisterNetEvent('police:client:EmergencySound', function()
PlaySound(-1, 'Event_Start_Text', 'GTAO_FM_Events_Soundset', false, 0, true)
end)
RegisterNetEvent('police:client:CallAnim', function()
local isCalling = true
local callCount = 5
lib.playAnim(cache.ped, 'cellphone@', 'cellphone_call_listen_base', 3.0, -1, -1, 49, 0, false, false, false)
Wait(1000)
CreateThread(function()
while isCalling do
Wait(1000)
callCount -= 1
if callCount <= 0 then
isCalling = false
StopAnimTask(cache.ped, 'cellphone@', 'cellphone_call_listen_base', 1.0)
end
end
end)
end)
RegisterNetEvent('police:client:ImpoundVehicle', function(fullImpound, price)
local coords = GetEntityCoords(cache.ped)
local vehicle = lib.getClosestVehicle(coords)
if not vehicle or not DoesEntityExist(vehicle) then return end
local bodyDamage = math.ceil(GetVehicleBodyHealth(vehicle))
local engineDamage = math.ceil(GetVehicleEngineHealth(vehicle))
local totalFuel = GetVehicleFuelLevel(vehicle)
if cache.vehicle or #(GetEntityCoords(cache.ped) - GetEntityCoords(vehicle)) > 5.0 then return end
if lib.progressCircle({
duration = 5000,
position = 'bottom',
label = locale('progressbar.impound'),
useWhileDead = false,
canCancel = true,
disable = {
move = true,
car = true,
combat = true,
mouse = false
},
anim = {
dict = 'missheistdockssetup1clipboard@base',
clip = 'base',
flags = 1
},
prop = {
{
model = `prop_notepad_01`,
bone = 18905,
pos = vec3(0.1, 0.02, 0.05),
rot = vec3(10.0, 0.0, 0.0)
},
{
model = 'prop_pencil_01',
bone = 58866,
pos = vec3(0.11, -0.02, 0.001),
rot = vec3(-120.0, 0.0, 0.0)
}
},
})
then
local plate = qbx.getVehiclePlate(vehicle)
TriggerServerEvent('police:server:Impound', plate, fullImpound, price, bodyDamage, engineDamage, totalFuel)
DeleteVehicle(vehicle)
exports.qbx_core:Notify(locale('success.impounded'), 'success')
else
exports.qbx_core:Notify(locale('error.canceled'), 'error')
end
ClearPedTasks(cache.ped)
end)
RegisterNetEvent('police:client:CheckStatus', function()
if QBX.PlayerData.job.type ~= 'leo' then return end
local playerId = lib.getClosestPlayer(GetEntityCoords(cache.ped), 5.0, false)
if not playerId then
return exports.qbx_core:Notify(locale('error.none_nearby'), 'error')
end
local result = lib.callback.await('police:GetPlayerStatus', false, playerId)
if not result then return end
for _, v in pairs(result) do
exports.qbx_core:Notify(v, 'success')
end
end)
function ToggleDuty()
TriggerServerEvent('QBCore:ToggleDuty')
TriggerServerEvent('police:server:UpdateCurrentCops')
end
if config.useTarget then
CreateThread(function()
for i = 1, #sharedConfig.locations.duty do
exports.ox_target:addBoxZone({
coords = sharedConfig.locations.duty[i],
size = vec3(1,1,3),
debug = config.polyDebug,
options = {{
distance = 1.5,
label = locale('info.onoff_duty'),
icon = 'fa-solid fa-sign-in-alt',
onSelect = ToggleDuty,
groups = 'police'
}}
})
end
end)
else
for i = 1, #sharedConfig.locations.duty do
lib.zones.box({
coords = sharedConfig.locations.duty[i],
size = vec3(2, 2, 2),
rotation = 0.0,
debug = config.polyDebug,
onEnter = function()
if QBX.PlayerData.job.type ~= 'leo' then return end
inPrompt = true
lib.showTextUI(locale(QBX.PlayerData.job.onduty and 'info.off_duty' or 'info.on_duty'))
uiPrompt('duty')
end,
onExit = function()
inPrompt = false
lib.hideTextUI()
end
})
end
end
CreateThread(function()
-- Police Trash
for i = 1, #sharedConfig.locations.trash do
lib.zones.box({
coords = sharedConfig.locations.trash[i],
size = vec3(2, 2, 2),
rotation = 0.0,
debug = config.polyDebug,
onEnter = function()
if QBX.PlayerData.job.type ~= 'leo' or not QBX.PlayerData.job.onduty then return end
inTrash = true
inPrompt = true
lib.showTextUI(locale('info.trash_enter'))
uiPrompt('trash', i)
end,
onExit = function()
inTrash = false
inPrompt = false
lib.hideTextUI()
end
})
end
-- Fingerprints
for i = 1, #sharedConfig.locations.fingerprint do
lib.zones.box({
coords = sharedConfig.locations.fingerprint[i],
size = vec3(2, 2, 2),
rotation = 0.0,
debug = config.polyDebug,
onEnter = function()
if QBX.PlayerData.job.type ~= 'leo' or not QBX.PlayerData.job.onduty then return end
inFingerprint = true
inPrompt = true
lib.showTextUI(locale('info.scan_fingerprint'))
uiPrompt('fingerprint')
end,
onExit = function()
inFingerprint = false
inPrompt = false
lib.hideTextUI()
end
})
end
-- Helicopter
for i = 1, #sharedConfig.locations.helicopter do
lib.zones.box({
coords = sharedConfig.locations.helicopter[i],
size = vec3(4, 4, 4),
rotation = 0.0,
debug = config.polyDebug,
onEnter = function()
if QBX.PlayerData.job.type ~= 'leo' or not QBX.PlayerData.job.onduty then return end
inHelicopter = true
inPrompt = true
uiPrompt('heli')
lib.showTextUI(locale(cache.vehicle and 'info.store_heli' or 'info.take_heli'))
end,
onExit = function()
inHelicopter = false
inPrompt = false
lib.hideTextUI()
end
})
end
-- Police Impound
for i = 1, #sharedConfig.locations.impound do
lib.zones.box({
coords = sharedConfig.locations.impound[i],
size = vec3(2, 2, 2),
rotation = 0.0,
debug = config.polyDebug,
onEnter = function()
if QBX.PlayerData.job.type ~= 'leo' or not QBX.PlayerData.job.onduty then return end
inImpound = true
inPrompt = true
currentGarage = i
lib.showTextUI(locale(cache.vehicle and 'info.impound_veh' or 'menu.pol_impound'))
uiPrompt('impound')
end,
onExit = function()
inImpound = false
inPrompt = false
lib.hideTextUI()
currentGarage = 0
end
})
end
-- Police Garage
for i = 1, #sharedConfig.locations.vehicle do
lib.zones.box({
coords = sharedConfig.locations.vehicle[i],
size = vec3(2, 2, 2),
rotation = 0.0,
debug = config.polyDebug,
onEnter = function()
if QBX.PlayerData.job.type ~= 'leo' or not QBX.PlayerData.job.onduty then return end
inGarage = true
inPrompt = true
currentGarage = i
lib.showTextUI(locale(cache.vehicle and 'info.store_veh' or 'info.grab_veh'))
uiPrompt('garage')
end,
onExit = function()
inGarage = false
inPrompt = false
lib.hideTextUI()
end
})
end
end)