Main/resources/[jobs]/[police]/ps-dispatch/client/main.lua
2025-06-07 08:51:21 +02:00

384 lines
12 KiB
Lua

QBCore = exports['qb-core']:GetCoreObject()
PlayerData = {}
inHuntingZone, inNoDispatchZone = false, false
local huntingZones, nodispatchZones, huntingBlips = {} , {}, {}
local blips = {}
local radius2 = {}
local alertsMuted = false
local alertsDisabled = false
local waypointCooldown = false
-- Functions
---@param bool boolean Toggles visibilty of the menu
local function toggleUI(bool)
SetNuiFocus(bool, bool)
SendNUIMessage({ action = "setVisible", data = bool })
end
-- Zone Functions --
local function removeZones()
-- Hunting Zone --
for i = 1, #huntingZones do
huntingZones[i]:remove()
end
-- No Dispatch Zone --
for i = 1, #nodispatchZones do
nodispatchZones[i]:remove()
end
-- Hunting Blips --
for i = 1, #huntingBlips do
RemoveBlip(huntingBlips[i])
end
-- Reset the stored values too
huntingZones, nodispatchZones, huntingBlips = {} , {}, {}
end
local function createZones()
-- Hunting Zone --
if Config.Locations['HuntingZones'][1] then
for _, hunting in pairs(Config.Locations["HuntingZones"]) do
-- Creates the Blips
if Config.EnableHuntingBlip then
local blip = AddBlipForCoord(hunting.coords.x, hunting.coords.y, hunting.coords.z)
local huntingradius = AddBlipForRadius(hunting.coords.x, hunting.coords.y, hunting.coords.z, hunting.radius)
SetBlipSprite(blip, 442)
SetBlipAsShortRange(blip, true)
SetBlipScale(blip, 0.8)
SetBlipColour(blip, 0)
SetBlipColour(huntingradius, 0)
SetBlipAlpha(huntingradius, 40)
BeginTextCommandSetBlipName("STRING")
AddTextComponentString(hunting.label)
EndTextCommandSetBlipName(blip)
huntingBlips[#huntingBlips+1] = blip
huntingBlips[#huntingBlips+1] = huntingradius
end
-- Creates the Sphere --
local huntingZone = lib.zones.sphere({
coords = hunting.coords,
radius = hunting.radius,
debug = Config.Debug,
onEnter = function()
inHuntingZone = true
end,
onExit = function()
inHuntingZone = false
end
})
huntingZones[#huntingZones+1] = huntingZone
end
end
-- No Dispatch Zone --
if Config.Locations['NoDispatchZones'][1] then
for _, nodispatch in pairs(Config.Locations["NoDispatchZones"]) do
local nodispatchZone = lib.zones.box({
coords = nodispatch.coords,
size = vec3(nodispatch.length, nodispatch.width, nodispatch.maxZ - nodispatch.minZ),
rotation = nodispatch.heading,
debug = Config.Debug,
onEnter = function()
inNoDispatchZone = true
end,
onExit = function()
inNoDispatchZone = false
end
})
nodispatchZones[#nodispatchZones+1] = nodispatchZone
end
end
end
local function setupDispatch()
local playerInfo = QBCore.Functions.GetPlayerData()
local locales = lib.getLocales()
PlayerData = {
charinfo = {
firstname = playerInfo.charinfo.firstname,
lastname = playerInfo.charinfo.lastname
},
metadata = {
callsign = playerInfo.metadata.callsign
},
citizenid = playerInfo.citizenid,
job = {
type = playerInfo.job.type,
name = playerInfo.job.name,
label = playerInfo.job.label
},
}
Wait(1000)
SendNUIMessage({
action = "setupUI",
data = {
locales = locales,
player = PlayerData,
keybind = Config.RespondKeybind,
maxCallList = Config.MaxCallList,
shortCalls = Config.ShortCalls,
}
})
end
---@param data string | table -- The player job or an array of jobs to check against
---@return boolean -- Returns true if the job is valid
local function isJobValid(data)
if PlayerData.job == nil then return false end
local jobType = PlayerData.job.type
local jobName = PlayerData.job.name
if type(data) == "string" then
return lib.table.contains(Config.Jobs, data) or lib.table.contains(Config.Jobs, jobName)
elseif type(data) == "table" then
return lib.table.contains(data, jobType) or lib.table.contains(data, jobName)
end
return false
end
local function openMenu()
if not isJobValid(PlayerData.job.type) then return end
local calls = lib.callback.await('ps-dispatch:callback:getCalls', false)
if #calls == 0 then
lib.notify({ description = locale('no_calls'), position = 'top', type = 'error' })
else
SendNUIMessage({ action = 'setDispatchs', data = calls, })
toggleUI(true)
end
end
local function setWaypoint()
if not isJobValid(PlayerData.job.type) then return end
if not IsOnDuty() then return end
local data = lib.callback.await('ps-dispatch:callback:getLatestDispatch', false)
if not data then return end
if data.alertTime == nil then data.alertTime = Config.AlertTime end
local timer = data.alertTime * 1000
if not waypointCooldown and lib.table.contains(data.jobs, PlayerData.job.type) then
SetNewWaypoint(data.coords.x, data.coords.y)
TriggerServerEvent('ps-dispatch:server:attach', data.id, PlayerData)
lib.notify({ description = locale('waypoint_set'), position = 'top', type = 'success' })
waypointCooldown = true
SetTimeout(timer, function()
waypointCooldown = false
end)
end
end
local function randomOffset(baseX, baseY, offset)
local randomX = baseX + math.random(-offset, offset)
local randomY = baseY + math.random(-offset, offset)
return randomX, randomY
end
local function createBlipData(coords, radius, sprite, color, scale, flash)
local blip = AddBlipForCoord(coords.x, coords.y, coords.z)
local radiusBlip = AddBlipForRadius(coords.x, coords.y, coords.z, radius)
SetBlipFlashes(blip, flash)
SetBlipSprite(blip, sprite or 161)
SetBlipHighDetail(blip, true)
SetBlipScale(blip, scale or 1.0)
SetBlipColour(blip, color or 84)
SetBlipAlpha(blip, 255)
SetBlipAsShortRange(blip, false)
SetBlipCategory(blip, 2)
SetBlipColour(radiusBlip, color or 84)
SetBlipAlpha(radiusBlip, 128)
return blip, radiusBlip
end
local function createBlip(data, blipData)
local blip, radius = nil, nil
local sprite = blipData.sprite or blipData.alert.sprite or 161
local color = blipData.color or blipData.alert.color or 84
local scale = blipData.scale or blipData.alert.scale or 1.0
local flash = blipData.flash or false
local alpha = 255
local radiusAlpha = 128
local blipWaitTime = ((blipData.length or blipData.alert.length) * 60000) / radiusAlpha
if blipData.offset then
local offsetX, offsetY = randomOffset(data.coords.x, data.coords.y, Config.MaxOffset)
blip, radius = createBlipData({ x = offsetX, y = offsetY, z = data.coords.z }, blipData.radius, sprite, color, scale, flash)
blips[data.id] = blip
radius2[data.id] = radius
else
blip, radius = createBlipData(data.coords, blipData.radius, sprite, color, scale, flash)
blips[data.id] = blip
radius2[data.id] = radius
end
BeginTextCommandSetBlipName('STRING')
AddTextComponentString(data.code .. ' - ' .. data.message)
EndTextCommandSetBlipName(blip)
while radiusAlpha > 0 do
Wait(blipWaitTime)
radiusAlpha = math.max(0, radiusAlpha - 1)
SetBlipAlpha(radius, radiusAlpha)
end
RemoveBlip(radius)
RemoveBlip(blip)
end
local function addBlip(data, blipData)
CreateThread(function()
createBlip(data, blipData)
end)
if not alertsMuted then
if blipData.sound == "Lose_1st" then
PlaySound(-1, blipData.sound, blipData.sound2, 0, 0, 1)
else
TriggerServerEvent("InteractSound_SV:PlayOnSource", blipData.sound or blipData.alert.sound, 0.25)
end
end
end
-- Keybind
local RespondToDispatch = lib.addKeybind({
name = 'RespondToDispatch',
description = 'Set waypoint to last call location',
defaultKey = Config.RespondKeybind,
onPressed = setWaypoint,
})
local OpenDispatchMenu = lib.addKeybind({
name = 'OpenDispatchMenu',
description = 'Open Dispatch Menu',
defaultKey = Config.OpenDispatchMenu,
onPressed = openMenu,
})
-- Events
RegisterNetEvent('ps-dispatch:client:notify', function(data, source)
if data.alertTime == nil then data.alertTime = Config.AlertTime end
local timer = data.alertTime * 1000
if alertsDisabled then return end
if not isJobValid(data.jobs) then return end
if not IsOnDuty() then return end
timerCheck = true
SendNUIMessage({
action = 'newCall',
data = {
data = data,
timer = timer,
}
})
addBlip(data, Config.Blips[data.codeName] or data.alert)
RespondToDispatch:disable(false)
OpenDispatchMenu:disable(true)
local startTime = GetGameTimer()
while timerCheck do
Wait(1000)
local currentTime = GetGameTimer()
local elapsed = currentTime - startTime
if elapsed >= timer then
break
end
end
timerCheck = false
OpenDispatchMenu:disable(false)
RespondToDispatch:disable(true)
end)
RegisterNetEvent('ps-dispatch:client:openMenu', function(data)
if not isJobValid(PlayerData.job.type) then return end
if not IsOnDuty() then return end
if #data == 0 then
lib.notify({ description = locale('no_calls'), position = 'top', type = 'error' })
else
toggleUI(true)
SendNUIMessage({ action = 'setDispatchs', data = data, })
end
end)
-- EventHandlers
RegisterNetEvent("QBCore:Client:OnJobUpdate", setupDispatch)
AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
setupDispatch()
createZones()
end)
AddEventHandler('QBCore:Client:OnPlayerUnload', removeZones)
AddEventHandler('onResourceStart', function(resourceName)
if resourceName ~= GetCurrentResourceName() then return end
setupDispatch()
end)
AddEventHandler('onResourceStop', function(resourceName)
if resourceName ~= GetCurrentResourceName() then return end
removeZones()
end)
-- NUICallbacks
RegisterNUICallback("hideUI", function(_, cb)
toggleUI(false)
cb("ok")
end)
RegisterNUICallback("attachUnit", function(data, cb)
TriggerServerEvent('ps-dispatch:server:attach', data.id, PlayerData)
SetNewWaypoint(data.coords.x, data.coords.y)
cb("ok")
end)
RegisterNUICallback("detachUnit", function(data, cb)
TriggerServerEvent('ps-dispatch:server:detach', data.id, PlayerData)
DeleteWaypoint()
cb("ok")
end)
RegisterNUICallback("toggleMute", function(data, cb)
local muteStatus = data.boolean and locale('muted') or locale('unmuted')
lib.notify({ description = locale('alerts') .. muteStatus, position = 'top', type = 'warning' })
alertsMuted = data.boolean
cb("ok")
end)
RegisterNUICallback("toggleAlerts", function(data, cb)
local muteStatus = data.boolean and locale('disabled') or locale('enabled')
lib.notify({ description = locale('alerts') .. muteStatus, position = 'top', type = 'warning' })
alertsDisabled = data.boolean
cb("ok")
end)
RegisterNUICallback("clearBlips", function(data, cb)
lib.notify({ description = locale('blips_cleared'), position = 'top', type = 'success' })
for k, v in pairs(blips) do
RemoveBlip(v)
end
for k, v in pairs(radius2) do
RemoveBlip(v)
end
cb("ok")
end)
RegisterNUICallback("refreshAlerts", function(data, cb)
lib.notify({ description = locale('alerts_refreshed'), position = 'top', type = 'success' })
local data = lib.callback.await('ps-dispatch:callback:getCalls', false)
SendNUIMessage({ action = 'setDispatchs', data = data, })
cb("ok")
end)