584 lines
19 KiB
Lua
584 lines
19 KiB
Lua
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)
|