1
0
Fork 0
forked from Simnation/Main
Main/resources/[carscripts]/qb-vehicle-tracker/client/client.lua

422 lines
16 KiB
Lua
Raw Normal View History

2025-07-01 09:08:25 +02:00
local QBCore = exports['qb-core']:GetCoreObject()
2025-07-01 09:13:57 +02:00
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
2025-07-01 09:34:18 +02:00
-- Ensure all config values exist
2025-07-01 09:13:57 +02:00
if not config.policeJobs then
config.policeJobs = {'police', 'sheriff'}
end
2025-07-01 09:34:18 +02:00
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
2025-07-01 07:06:04 +02:00
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
2025-07-01 08:20:46 +02:00
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
2025-07-01 09:08:25 +02:00
-- 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
2025-07-01 07:06:04 +02:00
-- Events
2025-07-01 08:02:31 +02:00
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
2025-07-01 08:20:46 +02:00
local displayName = tracker.name or tracker.vehiclePlate
2025-07-01 08:02:31 +02:00
table.insert(options, {
2025-07-01 08:20:46 +02:00
title = displayName,
description = (locale('vt_vehicle_plate') or "Vehicle Plate") .. ': ' .. tracker.vehiclePlate,
metadata = {
{label = locale('vt_serial_number') or "Serial Number", value = tracker.serialNumber}
},
2025-07-01 08:02:31 +02:00
icon = 'car',
2025-07-01 08:20:46 +02:00
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
2025-07-01 08:02:31 +02:00
})
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)
2025-07-01 07:06:04 +02:00
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')
2025-07-01 08:39:38 +02:00
-- 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
2025-07-01 09:08:25 +02:00
if isPlayerPolice() then
2025-07-01 08:39:38 +02:00
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
})
2025-07-01 07:06:04 +02:00
end
2025-07-01 08:39:38 +02:00
lib.registerContext({
id = 'scanner_menu',
title = locale('vt_scanner_menu_header') or "Tracker Scanner",
options = options
})
lib.showContext('scanner_menu')
2025-07-01 07:06:04 +02:00
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
2025-07-02 18:45:20 +02:00
lib.callback('qb_vehicle_tracker:getTrackedVehicleBySerial', false, function(veh, vehCoords, trackerName, isLastKnown)
2025-07-01 07:06:04 +02:00
if veh == nil then uiNotify(locale('vt_unable_connect'), 'error') return end
2025-07-02 18:45:20 +02:00
local blip = AddBlipForCoord(vehCoords.x, vehCoords.y, 0.0)
2025-07-01 07:06:04 +02:00
SetBlipSprite(blip, 161)
2025-07-02 18:45:20 +02:00
SetBlipColour(blip, isLastKnown and 3 or 1) -- Use yellow for last known position, red for current
2025-07-01 07:06:04 +02:00
SetBlipAlpha(blip, 250)
SetBlipDisplay(blip, 2)
SetBlipScale(blip, 2.5)
PulseBlip(blip)
SetBlipAsShortRange(blip, false)
BeginTextCommandSetBlipName('STRING')
2025-07-02 18:45:20 +02:00
local blipName = trackerName or ('Tracker ' .. veh)
if isLastKnown then
blipName = blipName .. " (Last Known)"
end
AddTextComponentSubstringPlayerName(blipName)
2025-07-01 07:06:04 +02:00
EndTextCommandSetBlipName(blip)
2025-07-02 18:45:20 +02:00
SetNewWaypoint(vehCoords.x, vehCoords.y)
2025-07-01 07:06:04 +02:00
trackedVehicles[serialNumber] = blip
playSound('10_SEC_WARNING', 'HUD_MINI_GAME_SOUNDSET')
2025-07-02 18:45:20 +02:00
if isLastKnown then
uiNotify(locale('vt_last_known_position') or "Showing last known position of vehicle", 'info')
else
uiNotify(locale('vt_connection_success'), 'success')
end
2025-07-01 07:06:04 +02:00
end, serialNumber)
end)
2025-07-02 18:45:20 +02:00
2025-07-01 08:39:38 +02:00
-- New events for advanced scanner features
RegisterNetEvent('qb_vehicle_tracker:client:locateTrackerOwner', function(vehiclePlate)
2025-07-01 09:34:18 +02:00
-- 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'})
2025-07-01 08:39:38 +02:00
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')
2025-07-01 09:34:18 +02:00
-- Remove the blip after configured time
SetTimeout(config.durations.ownerBlipDuration, function()
2025-07-01 08:39:38 +02:00
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)
2025-07-01 09:34:18 +02:00
-- Perform a more difficult skill check for police using configured difficulty
local success = lib.skillCheck(config.skillChecks.policeGetPhone, {'w', 'a', 's', 'd'})
2025-07-01 08:39:38 +02:00
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)
2025-07-01 07:06:04 +02:00
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
2025-07-01 08:02:31 +02:00
end)