forked from Simnation/Main
409 lines
16 KiB
Lua
409 lines
16 KiB
Lua
local QBCore = exports['qb-core']:GetCoreObject()
|
|
local config = {}
|
|
|
|
-- Try to load config, with fallback values
|
|
local success, result = pcall(function() return require('config') end)
|
|
if success then
|
|
config = result
|
|
else
|
|
-- Default config values if loading fails
|
|
config = {
|
|
trackerItem = 'vehicletracker',
|
|
trackerTabletItem = 'vehicletrackertablet',
|
|
trackerScannerItem = 'vehicletrackerscanner',
|
|
policeJobs = {'police', 'sheriff'}
|
|
}
|
|
end
|
|
|
|
-- Ensure all config values exist
|
|
if not config.policeJobs then
|
|
config.policeJobs = {'police', 'sheriff'}
|
|
end
|
|
|
|
if not config.skillChecks then
|
|
config.skillChecks = {
|
|
normalLocateOwner = {'easy', 'medium', 'medium'},
|
|
policeLocateOwner = {'easy', 'easy', 'medium'},
|
|
policeGetPhone = {'medium', 'medium', 'hard'}
|
|
}
|
|
end
|
|
|
|
if not config.durations then
|
|
config.durations = {
|
|
ownerBlipDuration = 60000 -- 60 seconds = 1 minute
|
|
}
|
|
end
|
|
|
|
local trackedVehicles = {}
|
|
|
|
lib.locale()
|
|
|
|
-- Functions
|
|
local function uiNotify(description, nType)
|
|
lib.notify({description = description, type = nType, position = 'center-right', iconAnimation = 'bounce', duration = 7000})
|
|
end
|
|
|
|
local function uiProgressBar(duration, label, anim, prop)
|
|
return lib.progressBar({
|
|
duration = duration,
|
|
label = label,
|
|
useWhileDead = false,
|
|
canCancel = true,
|
|
disable = { car = true, move = true, combat = true },
|
|
anim = anim,
|
|
prop = prop
|
|
})
|
|
end
|
|
|
|
local function playSound(audioName, audioDict)
|
|
local soundId = GetSoundId()
|
|
PlaySoundFrontend(soundId, audioName, audioDict, false)
|
|
SetTimeout(3000, function()
|
|
StopSound(soundId)
|
|
ReleaseSoundId(soundId)
|
|
end)
|
|
end
|
|
|
|
local function promptTrackerName(serialNumber, currentName)
|
|
local input = lib.inputDialog(locale('vt_name_tracker') or 'Name Your Tracker', {
|
|
{
|
|
type = 'input',
|
|
label = locale('vt_tracker_name') or 'Tracker Name',
|
|
description = locale('vt_name_description') or 'Enter a name to help identify this tracker',
|
|
default = currentName or '',
|
|
required = true
|
|
}
|
|
})
|
|
|
|
if not input or input[1] == '' then return false end
|
|
|
|
-- Update the tracker name on the server
|
|
lib.callback('qb_vehicle_tracker:updateTrackerName', false, function(success)
|
|
if success then
|
|
uiNotify(locale('vt_name_updated') or 'Tracker name updated', 'success')
|
|
else
|
|
uiNotify(locale('vt_name_failed') or 'Failed to update tracker name', 'error')
|
|
end
|
|
end, serialNumber, input[1])
|
|
|
|
return true
|
|
end
|
|
|
|
-- Function to check if player is police
|
|
local function isPlayerPolice()
|
|
local PlayerData = QBCore.Functions.GetPlayerData()
|
|
if not PlayerData or not PlayerData.job then return false end
|
|
|
|
for _, jobName in pairs(config.policeJobs) do
|
|
if PlayerData.job.name == jobName then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Events
|
|
RegisterNetEvent('qb_vehicle_tracker:client:showTrackerMenu', function(citizenid)
|
|
lib.callback('qb_vehicle_tracker:getPlayerTrackers', false, function(trackers)
|
|
if not trackers or #trackers == 0 then
|
|
uiNotify(locale('vt_no_trackers_found') or "No trackers found", 'error')
|
|
return
|
|
end
|
|
|
|
local options = {}
|
|
for _, tracker in ipairs(trackers) do
|
|
local displayName = tracker.name or tracker.vehiclePlate
|
|
|
|
table.insert(options, {
|
|
title = displayName,
|
|
description = (locale('vt_vehicle_plate') or "Vehicle Plate") .. ': ' .. tracker.vehiclePlate,
|
|
metadata = {
|
|
{label = locale('vt_serial_number') or "Serial Number", value = tracker.serialNumber}
|
|
},
|
|
icon = 'car',
|
|
onSelect = function()
|
|
-- Show submenu for this tracker
|
|
lib.registerContext({
|
|
id = 'tracker_options_' .. tracker.serialNumber,
|
|
title = displayName,
|
|
menu = 'vt_menu',
|
|
options = {
|
|
{
|
|
title = locale('vt_locate_tracker') or "Locate Vehicle",
|
|
description = locale('vt_locate_description') or "Show the vehicle's location on the map",
|
|
icon = 'location-dot',
|
|
onSelect = function()
|
|
TriggerEvent('qb_vehicle_tracker:client:locateTracker', tracker.serialNumber)
|
|
end
|
|
},
|
|
{
|
|
title = locale('vt_rename_tracker') or "Rename Tracker",
|
|
description = locale('vt_rename_description') or "Change the name of this tracker",
|
|
icon = 'pen',
|
|
onSelect = function()
|
|
promptTrackerName(tracker.serialNumber, tracker.name)
|
|
-- Refresh the menu after renaming
|
|
Wait(500)
|
|
TriggerEvent('qb_vehicle_tracker:client:showTrackerMenu', citizenid)
|
|
end
|
|
}
|
|
}
|
|
})
|
|
lib.showContext('tracker_options_' .. tracker.serialNumber)
|
|
end
|
|
})
|
|
end
|
|
|
|
lib.registerContext({
|
|
id = 'vt_menu',
|
|
title = locale('vt_menu_header') or "Vehicle Tracker",
|
|
options = options
|
|
})
|
|
|
|
if uiProgressBar(2000, locale('vt_pb_connecting') or "Connecting to tracker network...", {
|
|
dict = 'amb@code_human_in_bus_passenger_idles@female@tablet@base',
|
|
clip = 'base'
|
|
}, {
|
|
model = `prop_cs_tablet`,
|
|
pos = vec3(0.03, 0.002, -0.0),
|
|
rot = vec3(10.0, 160.0, 0.0)
|
|
}) then
|
|
lib.showContext('vt_menu')
|
|
else
|
|
uiNotify(locale('vt_pb_cancelled') or "Cancelled", 'error')
|
|
end
|
|
end, citizenid)
|
|
end)
|
|
|
|
RegisterNetEvent('qb_vehicle_tracker:client:scanTracker', function(slot)
|
|
local vehicle = lib.getClosestVehicle(GetEntityCoords(cache.ped), 3.0, true)
|
|
if vehicle == nil or not DoesEntityExist(vehicle) then uiNotify(locale('vt_no_vehicle_nearby'), 'error') return end
|
|
|
|
if uiProgressBar(6000, locale('vt_pb_scanning'), {
|
|
dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
|
|
clip = 'machinic_loop_mechandplayer',
|
|
flag = 1
|
|
}, {
|
|
model = `w_am_digiscanner`,
|
|
pos = vec3(0.06, 0.03, -0.1),
|
|
rot = vec3(10.0, 190.0, 0.0)
|
|
}) then
|
|
lib.callback('qb_vehicle_tracker:isVehicleTracked', false, function(veh)
|
|
if veh == nil then uiNotify(locale('vt_no_tracker'), 'info') return end
|
|
|
|
playSound('TIMER_STOP', 'HUD_MINI_GAME_SOUNDSET')
|
|
|
|
-- Create scanner options menu
|
|
local options = {
|
|
{
|
|
title = locale('vt_remove_tracker') or "Remove Tracker",
|
|
description = locale('vt_remove_description') or "Remove the tracking device from this vehicle",
|
|
icon = 'trash',
|
|
onSelect = function()
|
|
TriggerEvent('qb_vehicle_tracker:client:removeTracker', slot)
|
|
end
|
|
},
|
|
{
|
|
title = locale('vt_locate_owner') or "Locate Tracker Owner",
|
|
description = locale('vt_locate_owner_description') or "Try to trace the signal back to the owner",
|
|
icon = 'satellite-dish',
|
|
onSelect = function()
|
|
TriggerEvent('qb_vehicle_tracker:client:locateTrackerOwner', GetVehicleNumberPlateText(vehicle))
|
|
end
|
|
}
|
|
}
|
|
|
|
-- Add police-only option to get phone number
|
|
if isPlayerPolice() then
|
|
table.insert(options, {
|
|
title = locale('vt_get_phone') or "Get Owner's Phone Number",
|
|
description = locale('vt_get_phone_description') or "Try to extract the owner's phone number",
|
|
icon = 'phone',
|
|
onSelect = function()
|
|
TriggerEvent('qb_vehicle_tracker:client:getTrackerOwnerPhone', GetVehicleNumberPlateText(vehicle))
|
|
end
|
|
})
|
|
end
|
|
|
|
lib.registerContext({
|
|
id = 'scanner_menu',
|
|
title = locale('vt_scanner_menu_header') or "Tracker Scanner",
|
|
options = options
|
|
})
|
|
|
|
lib.showContext('scanner_menu')
|
|
end, GetVehicleNumberPlateText(vehicle))
|
|
else
|
|
uiNotify(locale('vt_pb_cancelled'), 'error')
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb_vehicle_tracker:client:placeTracker', function(slot, serialNumber)
|
|
local vehicle = lib.getClosestVehicle(GetEntityCoords(cache.ped), 2.5, true)
|
|
if vehicle == nil or not DoesEntityExist(vehicle) then uiNotify(locale('vt_no_vehicle_nearby'), 'error') return end
|
|
|
|
if uiProgressBar(6000, locale('vt_pb_placing'), {
|
|
dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
|
|
clip = 'machinic_loop_mechandplayer',
|
|
flag = 1
|
|
}, {
|
|
model = `prop_prototype_minibomb`,
|
|
pos = vec3(0.1, 0.03, -0.0),
|
|
rot = vec3(10.0, 160.0, 0.0)
|
|
}) then
|
|
lib.callback('qb_vehicle_tracker:placeTracker', false, function(success)
|
|
if not success then return end
|
|
playSound('Hack_Success', 'DLC_HEIST_BIOLAB_PREP_HACKING_SOUNDS')
|
|
uiNotify(locale('vt_placed_success'), 'success')
|
|
end, GetVehicleNumberPlateText(vehicle), slot, serialNumber)
|
|
else
|
|
uiNotify(locale('vt_pb_cancelled'), 'error')
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb_vehicle_tracker:client:removeTracker', function(slot)
|
|
local vehicle = lib.getClosestVehicle(GetEntityCoords(cache.ped), 3.0, true)
|
|
if vehicle == nil or not DoesEntityExist(vehicle) then uiNotify(locale('vt_no_vehicle_nearby'), 'error') return end
|
|
|
|
local vehPlate = GetVehicleNumberPlateText(vehicle)
|
|
|
|
lib.callback('qb_vehicle_tracker:isVehicleTracked', false, function(veh)
|
|
if veh == nil then return uiNotify(locale('vt_no_tracker'), 'info') end
|
|
if uiProgressBar(6000, locale('vt_pb_removing'), {
|
|
dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
|
|
clip = 'machinic_loop_mechandplayer',
|
|
flag = 1
|
|
}, {}) then
|
|
lib.callback('qb_vehicle_tracker:removeTracker', false, function(success)
|
|
if not success then return end
|
|
|
|
if trackedVehicles[veh.serialNumber] then
|
|
RemoveBlip(trackedVehicles[veh.serialNumber])
|
|
|
|
trackedVehicles[veh.serialNumber] = nil
|
|
end
|
|
|
|
playSound('Hack_Success', 'DLC_HEIST_BIOLAB_PREP_HACKING_SOUNDS')
|
|
uiNotify(locale('vt_remove_success'), 'success')
|
|
end, vehPlate, slot)
|
|
else
|
|
uiNotify(locale('vt_pb_cancelled'), 'error')
|
|
end
|
|
end, vehPlate)
|
|
end)
|
|
|
|
RegisterNetEvent('qb_vehicle_tracker:client:locateTracker', function(serialNumber)
|
|
if serialNumber == nil then uiNotify(locale('vt_not_placed'), 'error') return end
|
|
|
|
lib.callback('qb_vehicle_tracker:getTrackedVehicleBySerial', false, function(veh, vehCoords, trackerName)
|
|
if veh == nil then uiNotify(locale('vt_unable_connect'), 'error') return end
|
|
|
|
local blip = AddBlipForCoord(vehCoords.x , vehCoords.y, 0.0)
|
|
|
|
SetBlipSprite(blip, 161)
|
|
SetBlipColour(blip, 1)
|
|
SetBlipAlpha(blip, 250)
|
|
SetBlipDisplay(blip, 2)
|
|
SetBlipScale(blip, 2.5)
|
|
PulseBlip(blip)
|
|
SetBlipAsShortRange(blip, false)
|
|
BeginTextCommandSetBlipName('STRING')
|
|
AddTextComponentSubstringPlayerName(trackerName or ('Tracker ' .. veh))
|
|
EndTextCommandSetBlipName(blip)
|
|
SetNewWaypoint(vehCoords.x , vehCoords.y)
|
|
|
|
trackedVehicles[serialNumber] = blip
|
|
|
|
playSound('10_SEC_WARNING', 'HUD_MINI_GAME_SOUNDSET')
|
|
uiNotify(locale('vt_connection_success'), 'success')
|
|
end, serialNumber)
|
|
end)
|
|
|
|
-- New events for advanced scanner features
|
|
RegisterNetEvent('qb_vehicle_tracker:client:locateTrackerOwner', function(vehiclePlate)
|
|
-- Determine which skill check difficulty to use based on player job
|
|
local skillCheckDifficulty = config.skillChecks.normalLocateOwner
|
|
if isPlayerPolice() then
|
|
skillCheckDifficulty = config.skillChecks.policeLocateOwner
|
|
end
|
|
|
|
-- Perform a skill check with appropriate difficulty
|
|
local success = lib.skillCheck(skillCheckDifficulty, {'w', 'a', 's', 'd'})
|
|
|
|
if success then
|
|
lib.callback('qb_vehicle_tracker:getTrackerOwnerLocation', false, function(ownerCoords)
|
|
if not ownerCoords then
|
|
uiNotify(locale('vt_owner_not_found') or "Could not trace the signal back to the owner", 'error')
|
|
return
|
|
end
|
|
|
|
local blip = AddBlipForCoord(ownerCoords.x, ownerCoords.y, 0.0)
|
|
|
|
SetBlipSprite(blip, 280)
|
|
SetBlipColour(blip, 1)
|
|
SetBlipAlpha(blip, 250)
|
|
SetBlipDisplay(blip, 2)
|
|
SetBlipScale(blip, 1.0)
|
|
PulseBlip(blip)
|
|
SetBlipAsShortRange(blip, false)
|
|
BeginTextCommandSetBlipName('STRING')
|
|
AddTextComponentSubstringPlayerName(locale('vt_tracker_owner') or "Tracker Owner")
|
|
EndTextCommandSetBlipName(blip)
|
|
SetNewWaypoint(ownerCoords.x, ownerCoords.y)
|
|
|
|
-- Store the blip with a unique key
|
|
local uniqueKey = "owner_" .. vehiclePlate
|
|
trackedVehicles[uniqueKey] = blip
|
|
|
|
playSound('Hack_Success', 'DLC_HEIST_BIOLAB_PREP_HACKING_SOUNDS')
|
|
uiNotify(locale('vt_owner_located') or "Successfully traced signal to the owner's location", 'success')
|
|
|
|
-- Remove the blip after configured time
|
|
SetTimeout(config.durations.ownerBlipDuration, function()
|
|
if trackedVehicles[uniqueKey] then
|
|
RemoveBlip(trackedVehicles[uniqueKey])
|
|
trackedVehicles[uniqueKey] = nil
|
|
end
|
|
end)
|
|
end, vehiclePlate)
|
|
else
|
|
uiNotify(locale('vt_trace_failed') or "Failed to trace the signal", 'error')
|
|
playSound('Failure', 'DLC_HEIST_HACKING_SNAKE_SOUNDS')
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb_vehicle_tracker:client:getTrackerOwnerPhone', function(vehiclePlate)
|
|
-- Perform a more difficult skill check for police using configured difficulty
|
|
local success = lib.skillCheck(config.skillChecks.policeGetPhone, {'w', 'a', 's', 'd'})
|
|
|
|
if success then
|
|
lib.callback('qb_vehicle_tracker:getTrackerOwnerPhone', false, function(phoneNumber)
|
|
if not phoneNumber then
|
|
uiNotify(locale('vt_phone_not_found') or "Could not extract phone number from the tracker", 'error')
|
|
return
|
|
end
|
|
|
|
playSound('Hack_Success', 'DLC_HEIST_BIOLAB_PREP_HACKING_SOUNDS')
|
|
uiNotify((locale('vt_phone_found') or "Phone number extracted: ") .. phoneNumber, 'success')
|
|
end, vehiclePlate)
|
|
else
|
|
uiNotify(locale('vt_phone_extraction_failed') or "Failed to extract phone number", 'error')
|
|
playSound('Failure', 'DLC_HEIST_HACKING_SNAKE_SOUNDS')
|
|
end
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(3000)
|
|
for serialNumber, blip in pairs(trackedVehicles) do
|
|
local blipAlpha = GetBlipAlpha(blip)
|
|
if blipAlpha > 0 then
|
|
SetBlipAlpha(blip, blipAlpha - 10)
|
|
else
|
|
trackedVehicles[serialNumber] = nil
|
|
RemoveBlip(blip)
|
|
end
|
|
end
|
|
end
|
|
end)
|