229 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local QBCore = exports['qb-core']:GetCoreObject()
 | |
| local config = require 'config'
 | |
| 
 | |
| -- Database functions
 | |
| local function deleteOldTrackers()
 | |
|     return MySQL.query.await('DELETE FROM `vehicle_trackers` WHERE startedAt < (NOW() - INTERVAL 7 DAY)')
 | |
| end
 | |
| 
 | |
| local function addTracker(serialNumber, vehiclePlate, owner, name, phoneNumber)
 | |
|     return MySQL.prepare.await('INSERT INTO `vehicle_trackers` (`serialNumber`, `vehiclePlate`, `owner`, `name`, `phoneNumber`) VALUES (?, ?, ?, ?, ?)', 
 | |
|     { serialNumber, vehiclePlate, owner, name, phoneNumber })
 | |
| end
 | |
| 
 | |
| local function deleteTracker(vehiclePlate)
 | |
|     return MySQL.prepare.await('DELETE FROM `vehicle_trackers` WHERE `vehiclePlate` = ?', { vehiclePlate })
 | |
| end
 | |
| 
 | |
| local function getTracker(serialNumber)
 | |
|     return MySQL.single.await('SELECT `serialNumber`, `vehiclePlate`, `name`, `phoneNumber`, `owner` FROM `vehicle_trackers` WHERE `serialNumber` = ? LIMIT 1', { serialNumber })
 | |
| end
 | |
| 
 | |
| local function isTracked(vehiclePlate)
 | |
|     return MySQL.scalar.await('SELECT `serialNumber` FROM `vehicle_trackers` WHERE `vehiclePlate` = ? LIMIT 1', { vehiclePlate })
 | |
| end
 | |
| 
 | |
| local function getPlayerTrackers(owner)
 | |
|     return MySQL.query.await('SELECT `serialNumber`, `vehiclePlate`, `name` FROM `vehicle_trackers` WHERE `owner` = ?', { owner })
 | |
| end
 | |
| 
 | |
| local function updateTrackerName(serialNumber, name)
 | |
|     return MySQL.prepare.await('UPDATE `vehicle_trackers` SET `name` = ? WHERE `serialNumber` = ?', 
 | |
|     { name, serialNumber })
 | |
| end
 | |
| 
 | |
| -- Utility functions
 | |
| local function getRandomSerialNumber()
 | |
|     return lib.string.random('...........')
 | |
| end
 | |
| 
 | |
| local function trim(plate)
 | |
|     return (plate:gsub("^%s*(.-)%s*$", "%1"))
 | |
| end
 | |
| 
 | |
| local function getVehicleNetworkIdByPlate(vehiclePlate)
 | |
|     local vehicles = GetAllVehicles()
 | |
| 
 | |
|     for _, vehicle in ipairs(vehicles) do
 | |
|         if trim(GetVehicleNumberPlateText(vehicle)) == trim(vehiclePlate) then
 | |
|             return NetworkGetNetworkIdFromEntity(vehicle)
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     return nil
 | |
| end
 | |
| 
 | |
| local function isPlayerNearVehicle(playerCoords, vehiclePlate)
 | |
|     local vehicle = lib.getClosestVehicle(playerCoords, 3.0, true)
 | |
| 
 | |
|     if not vehicle or not DoesEntityExist(vehicle) or GetVehicleNumberPlateText(vehicle) ~= vehiclePlate then
 | |
|         return false
 | |
|     end
 | |
| 
 | |
|     return true
 | |
| end
 | |
| 
 | |
| local function getPlayerPhoneNumber(citizenid)
 | |
|     local player = MySQL.single.await('SELECT phone_number FROM players WHERE citizenid = ?', { citizenid })
 | |
|     if player then
 | |
|         return player.phone_number
 | |
|     end
 | |
|     return nil
 | |
| end
 | |
| 
 | |
| -- QB Usable Items
 | |
| QBCore.Functions.CreateUseableItem(config.trackerItem, function(source, item)
 | |
|     TriggerClientEvent('qb_vehicle_tracker:client:placeTracker', source, item.slot, getRandomSerialNumber())
 | |
| end)
 | |
| 
 | |
| QBCore.Functions.CreateUseableItem(config.trackerTabletItem, function(source, item)
 | |
|     local Player = QBCore.Functions.GetPlayer(source)
 | |
|     TriggerClientEvent('qb_vehicle_tracker:client:showTrackerMenu', source, Player.PlayerData.citizenid)
 | |
| end)
 | |
| 
 | |
| QBCore.Functions.CreateUseableItem(config.trackerScannerItem, function(source, item)
 | |
|     TriggerClientEvent('qb_vehicle_tracker:client:scanTracker', source, item.slot)
 | |
| end)
 | |
| 
 | |
| -- Event Handler
 | |
| AddEventHandler('onResourceStart', function(resourceName)
 | |
|     if GetCurrentResourceName() == resourceName then
 | |
|         deleteOldTrackers()
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Callbacks
 | |
| lib.callback.register('qb_vehicle_tracker:getTrackedVehicleBySerial', function(_, serialNumber)
 | |
|     if type(serialNumber) ~= "string" or string.len(serialNumber) < 11 then return end
 | |
| 
 | |
|     local tracker = getTracker(serialNumber)
 | |
|     if not tracker then return end
 | |
| 
 | |
|     local vehicleNetworkID = getVehicleNetworkIdByPlate(tracker.vehiclePlate)
 | |
|     if not vehicleNetworkID then return end
 | |
| 
 | |
|     local vehicleEntity = NetworkGetEntityFromNetworkId(vehicleNetworkID)
 | |
|     if not DoesEntityExist(vehicleEntity) then return end
 | |
| 
 | |
|     local vehCoords = GetEntityCoords(vehicleEntity)
 | |
| 
 | |
|     return tracker.vehiclePlate, vector2(vehCoords.x, vehCoords.y), tracker.name
 | |
| end)
 | |
| 
 | |
| lib.callback.register('qb_vehicle_tracker:isVehicleTracked', function(source, vehiclePlate)
 | |
|     if type(vehiclePlate) ~= "string" or not isPlayerNearVehicle(GetEntityCoords(GetPlayerPed(source)), vehiclePlate) then
 | |
|         return false
 | |
|     end
 | |
| 
 | |
|     return isTracked(trim(vehiclePlate))
 | |
| end)
 | |
| 
 | |
| lib.callback.register('qb_vehicle_tracker:placeTracker', function(source, vehiclePlate, slot, serialNumber)
 | |
|     if type(vehiclePlate) ~= "string" or type(serialNumber) ~= "string" or string.len(serialNumber) < 11 then return false end
 | |
|     if not isPlayerNearVehicle(GetEntityCoords(GetPlayerPed(source)), vehiclePlate) then return false end
 | |
|     
 | |
|     local Player = QBCore.Functions.GetPlayer(source)
 | |
|     local defaultName = "Tracker " .. trim(vehiclePlate)
 | |
|     local phoneNumber = getPlayerPhoneNumber(Player.PlayerData.citizenid) or "Unknown"
 | |
|     
 | |
|     if not addTracker(serialNumber, trim(vehiclePlate), Player.PlayerData.citizenid, defaultName, phoneNumber) then return false end
 | |
| 
 | |
|     Player.Functions.RemoveItem(config.trackerItem, 1, slot)
 | |
|     TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items[config.trackerItem], 'remove')
 | |
| 
 | |
|     return true
 | |
| end)
 | |
| 
 | |
| lib.callback.register('qb_vehicle_tracker:removeTracker', function(source, vehiclePlate, slot)
 | |
|     if type(vehiclePlate) ~= "string" or not isPlayerNearVehicle(GetEntityCoords(GetPlayerPed(source)), vehiclePlate) then
 | |
|         return false
 | |
|     end
 | |
| 
 | |
|     if not deleteTracker(trim(vehiclePlate)) then return false end
 | |
| 
 | |
|     local Player = QBCore.Functions.GetPlayer(source)
 | |
|     if Player.Functions.RemoveItem(config.trackerScannerItem, 1, slot) then
 | |
|         TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items[config.trackerScannerItem], 'remove')
 | |
|     end
 | |
| 
 | |
|     return true
 | |
| end)
 | |
| 
 | |
| lib.callback.register('qb_vehicle_tracker:getPlayerTrackers', function(source, citizenid)
 | |
|     local Player = QBCore.Functions.GetPlayer(source)
 | |
|     if Player.PlayerData.citizenid ~= citizenid then return {} end
 | |
|     
 | |
|     return getPlayerTrackers(citizenid)
 | |
| end)
 | |
| 
 | |
| lib.callback.register('qb_vehicle_tracker:updateTrackerName', function(source, serialNumber, name)
 | |
|     if type(serialNumber) ~= "string" or string.len(serialNumber) < 11 or type(name) ~= "string" then return false end
 | |
|     
 | |
|     local Player = QBCore.Functions.GetPlayer(source)
 | |
|     
 | |
|     -- Get the tracker to verify it exists
 | |
|     local tracker = getTracker(serialNumber)
 | |
|     if not tracker then return false end
 | |
|     
 | |
|     -- Verify the player owns trackers with this serial number
 | |
|     local playerTrackers = getPlayerTrackers(Player.PlayerData.citizenid)
 | |
|     local isOwner = false
 | |
|     
 | |
|     for _, t in ipairs(playerTrackers) do
 | |
|         if t.serialNumber == serialNumber then
 | |
|             isOwner = true
 | |
|             break
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     if not isOwner then return false end
 | |
|     
 | |
|     -- Update the name
 | |
|     return updateTrackerName(serialNumber, name)
 | |
| end)
 | |
| 
 | |
| -- New callbacks for advanced scanner features
 | |
| lib.callback.register('qb_vehicle_tracker:getTrackerOwnerLocation', function(source, vehiclePlate)
 | |
|     if type(vehiclePlate) ~= "string" then return nil end
 | |
|     
 | |
|     local serialNumber = isTracked(trim(vehiclePlate))
 | |
|     if not serialNumber then return nil end
 | |
|     
 | |
|     local tracker = getTracker(serialNumber)
 | |
|     if not tracker or not tracker.owner then return nil end
 | |
|     
 | |
|     local targetPlayer = QBCore.Functions.GetPlayerByCitizenId(tracker.owner)
 | |
|     if not targetPlayer then return nil end
 | |
|     
 | |
|     local targetPed = GetPlayerPed(targetPlayer.PlayerData.source)
 | |
|     if not targetPed then return nil end
 | |
|     
 | |
|     local targetCoords = GetEntityCoords(targetPed)
 | |
|     return vector2(targetCoords.x, targetCoords.y)
 | |
| end)
 | |
| 
 | |
| lib.callback.register('qb_vehicle_tracker:getTrackerOwnerPhone', function(source, vehiclePlate)
 | |
|     local Player = QBCore.Functions.GetPlayer(source)
 | |
|     if not Player then return nil end
 | |
|     
 | |
|     -- Check if player is police
 | |
|     local isPolice = false
 | |
|     for _, jobName in pairs(config.policeJobs) do
 | |
|         if Player.PlayerData.job.name == jobName then
 | |
|             isPolice = true
 | |
|             break
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     if not isPolice then return nil end
 | |
|     
 | |
|     if type(vehiclePlate) ~= "string" then return nil end
 | |
|     
 | |
|     local serialNumber = isTracked(trim(vehiclePlate))
 | |
|     if not serialNumber then return nil end
 | |
|     
 | |
|     local tracker = getTracker(serialNumber)
 | |
|     if not tracker or not tracker.phoneNumber then return nil end
 | |
|     
 | |
|     return tracker.phoneNumber
 | |
| end)
 | 
