384 lines
12 KiB
Lua
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)
|