-- Variables local config = require 'config.client' local sharedConfig = require 'config.shared' local isLoggedIn = LocalPlayer.state.isLoggedIn local meterIsOpen = false local meterActive = false local lastLocation = nil local mouseActive = false local garageZone, taxiParkingZone = nil, nil -- used for polyzones local isInsidePickupZone = false local isInsideDropZone = false local meterData = { fareAmount = 6, currentFare = 0, distanceTraveled = 0 } local NpcData = { Active = false, CurrentNpc = nil, LastNpc = nil, CurrentDeliver = nil, LastDeliver = nil, Npc = nil, NpcBlip = nil, DeliveryBlip = nil, NpcTaken = false, NpcDelivered = false, CountDown = 180 } local taxiPed = nil local function resetNpcTask() NpcData = { Active = false, CurrentNpc = nil, LastNpc = nil, CurrentDeliver = nil, LastDeliver = nil, Npc = nil, NpcBlip = nil, DeliveryBlip = nil, NpcTaken = false, NpcDelivered = false } end local function resetMeter() meterData = { fareAmount = 6, currentFare = 0, distanceTraveled = 0 } end local function whitelistedVehicle() local veh = GetEntityModel(cache.vehicle) local retval = false for i = 1, #config.allowedVehicles, 1 do if veh == joaat(config.allowedVehicles[i].model) then retval = true end end if veh == `dynasty` then retval = true end return retval end local function isDriver() return cache.seat == -1 end local zone local delieveryZone local function getDeliveryLocation() NpcData.CurrentDeliver = math.random(1, #sharedConfig.npcLocations.deliverLocations) if NpcData.LastDeliver then while NpcData.LastDeliver ~= NpcData.CurrentDeliver do NpcData.CurrentDeliver = math.random(1, #sharedConfig.npcLocations.deliverLocations) end end if NpcData.DeliveryBlip then RemoveBlip(NpcData.DeliveryBlip) end NpcData.DeliveryBlip = AddBlipForCoord(sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].x, sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].y, sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].z) SetBlipColour(NpcData.DeliveryBlip, 3) SetBlipRoute(NpcData.DeliveryBlip, true) SetBlipRouteColour(NpcData.DeliveryBlip, 3) NpcData.LastDeliver = NpcData.CurrentDeliver if not config.useTarget then -- added checks to disable distance checking if polyzone option is used CreateThread(function() while true do local pos = GetEntityCoords(cache.ped) local dist = #(pos - vec3(sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].x, sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].y, sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].z)) if dist < 20 then DrawMarker(2, sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].x, sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].y, sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3, 0.3, 0.3, 255, 255, 255, 255, false, false, 0, true, nil, nil, false) if dist < 5 then qbx.drawText3d({text = locale('info.drop_off_npc'), coords = sharedConfig.npcLocations.deliverLocations[NpcData.CurrentDeliver].xyz}) if IsControlJustPressed(0, 38) then TaskLeaveVehicle(NpcData.Npc, cache.vehicle, 0) SetEntityAsMissionEntity(NpcData.Npc, false, true) SetEntityAsNoLongerNeeded(NpcData.Npc) local targetCoords = sharedConfig.npcLocations.takeLocations[NpcData.LastNpc] TaskGoStraightToCoord(NpcData.Npc, targetCoords.x, targetCoords.y, targetCoords.z, 1.0, -1, 0.0, 0.0) SendNUIMessage({ action = 'toggleMeter' }) TriggerServerEvent('qb-taxi:server:NpcPay', meterData.currentFare) meterActive = false SendNUIMessage({ action = 'resetMeter' }) exports.qbx_core:Notify(locale('info.person_was_dropped_off'), 'success') if NpcData.DeliveryBlip then RemoveBlip(NpcData.DeliveryBlip) end local RemovePed = function(p) SetTimeout(60000, function() DeletePed(p) end) end RemovePed(NpcData.Npc) resetNpcTask() break end end end Wait(0) end end) end end local function callNpcPoly() CreateThread(function() while not NpcData.NpcTaken do if isInsidePickupZone then if IsControlJustPressed(0, 38) then lib.hideTextUI() local veh = cache.vehicle local maxSeats, freeSeat = GetVehicleMaxNumberOfPassengers(veh), 0 for i= maxSeats - 1, 0, -1 do if IsVehicleSeatFree(veh, i) then freeSeat = i break end end meterIsOpen = true meterActive = true lastLocation = GetEntityCoords(cache.ped) SendNUIMessage({ action = 'openMeter', toggle = true, meterData = config.meter }) SendNUIMessage({ action = 'toggleMeter' }) ClearPedTasksImmediately(NpcData.Npc) FreezeEntityPosition(NpcData.Npc, false) TaskEnterVehicle(NpcData.Npc, veh, -1, freeSeat, 1.0, 0) exports.qbx_core:Notify(locale('info.go_to_location'), 'inform') if NpcData.NpcBlip then RemoveBlip(NpcData.NpcBlip) end getDeliveryLocation() NpcData.NpcTaken = true createNpcDelieveryLocation() zone:remove() lib.hideTextUI() end end Wait(0) end end) end local function onEnterCallZone() if whitelistedVehicle() and not isInsidePickupZone and not NpcData.NpcTaken then isInsidePickupZone = true lib.showTextUI(locale('info.call_npc'), {position = 'left-center'}) callNpcPoly() end end local function onExitCallZone() lib.hideTextUI() isInsidePickupZone = false end local function createNpcPickUpLocation() zone = lib.zones.box({ coords = config.pzLocations.takeLocations[NpcData.CurrentNpc].coord, size = vec3(config.pzLocations.takeLocations[NpcData.CurrentNpc].height, config.pzLocations.takeLocations[NpcData.CurrentNpc].width, (config.pzLocations.takeLocations[NpcData.CurrentNpc].maxZ - config.pzLocations.takeLocations[NpcData.CurrentNpc].minZ)), rotation = config.pzLocations.takeLocations[NpcData.CurrentNpc].heading, debug = config.debugPoly, onEnter = onEnterCallZone, onExit = onExitCallZone }) end local function enumerateEntitiesWithinDistance(entities, isPlayerEntities, coords, maxDistance) local nearbyEntities = {} if coords then coords = vec3(coords.x, coords.y, coords.z) else coords = GetEntityCoords(cache.ped) end for k, entity in pairs(entities) do local distance = #(coords - GetEntityCoords(entity)) if distance <= maxDistance then nearbyEntities[#nearbyEntities + 1] = isPlayerEntities and k or entity end end return nearbyEntities end local function getVehiclesInArea(coords, maxDistance) -- Vehicle inspection in designated area return enumerateEntitiesWithinDistance(GetGamePool('CVehicle'), false, coords, maxDistance) end local function isSpawnPointClear(coords, maxDistance) -- Check the spawn point to see if it's empty or not: return #getVehiclesInArea(coords, maxDistance) == 0 end local function getVehicleSpawnPoint() local near = nil local distance = 10000 for k, v in pairs(config.cabSpawns) do if isSpawnPointClear(vec3(v.x, v.y, v.z), 2.5) then local pos = GetEntityCoords(cache.ped) local cur_distance = #(pos - vec3(v.x, v.y, v.z)) if cur_distance < distance then distance = cur_distance near = k end end end return near end local function calculateFareAmount() if meterIsOpen and meterActive then local startPos = lastLocation local newPos = GetEntityCoords(cache.ped) if startPos ~= newPos then local newDistance = #(startPos - newPos) lastLocation = newPos meterData['distanceTraveled'] += (newDistance / 1609) local fareAmount = ((meterData['distanceTraveled']) * config.meter.defaultPrice) + config.meter.startingPrice meterData['currentFare'] = math.floor(fareAmount) SendNUIMessage({ action = 'updateMeter', meterData = meterData }) end end end local function onEnterDropZone() if whitelistedVehicle() and not isInsideDropZone and NpcData.NpcTaken then isInsideDropZone = true lib.showTextUI(locale('info.drop_off_npc'), {position = 'left-center'}) dropNpcPoly() end end local function onExitDropZone() lib.hideTextUI() isInsideDropZone = false end function createNpcDelieveryLocation() delieveryZone = lib.zones.box({ coords = config.pzLocations.dropLocations[NpcData.CurrentDeliver].coord, size = vec3(config.pzLocations.dropLocations[NpcData.CurrentDeliver].height, config.pzLocations.dropLocations[NpcData.CurrentDeliver].width, (config.pzLocations.dropLocations[NpcData.CurrentDeliver].maxZ - config.pzLocations.dropLocations[NpcData.CurrentDeliver].minZ)), rotation = config.pzLocations.dropLocations[NpcData.CurrentDeliver].heading, debug = config.debugPoly, onEnter = onEnterDropZone, onExit = onExitDropZone }) end function dropNpcPoly() CreateThread(function() while NpcData.NpcTaken do if isInsideDropZone then if IsControlJustPressed(0, 38) then lib.hideTextUI() local veh = cache.vehicle TaskLeaveVehicle(NpcData.Npc, veh, 0) Wait(1000) SetVehicleDoorShut(veh, 3, false) SetEntityAsMissionEntity(NpcData.Npc, false, true) SetEntityAsNoLongerNeeded(NpcData.Npc) local targetCoords = sharedConfig.npcLocations.takeLocations[NpcData.LastNpc] TaskGoStraightToCoord(NpcData.Npc, targetCoords.x, targetCoords.y, targetCoords.z, 1.0, -1, 0.0, 0.0) SendNUIMessage({ action = 'toggleMeter' }) TriggerServerEvent('qb-taxi:server:NpcPay', meterData.currentFare) meterActive = false SendNUIMessage({ action = 'resetMeter' }) exports.qbx_core:Notify(locale('info.person_was_dropped_off'), 'success') if NpcData.DeliveryBlip ~= nil then RemoveBlip(NpcData.DeliveryBlip) end local RemovePed = function(p) SetTimeout(60000, function() DeletePed(p) end) end RemovePed(NpcData.Npc) resetNpcTask() delieveryZone:remove() lib.hideTextUI() break end end Wait(0) end end) end local function setLocationsBlip() if not config.useBlips then return end local taxiBlip = AddBlipForCoord(config.locations.main.coords.x, config.locations.main.coords.y, config.locations.main.coords.z) SetBlipSprite(taxiBlip, 198) SetBlipDisplay(taxiBlip, 4) SetBlipScale(taxiBlip, 0.6) SetBlipAsShortRange(taxiBlip, true) SetBlipColour(taxiBlip, 5) BeginTextCommandSetBlipName('STRING') AddTextComponentSubstringPlayerName(locale('info.blip_name')) EndTextCommandSetBlipName(taxiBlip) end local function taxiGarage() local registeredMenu = { id = 'garages_depotlist', title = locale('menu.taxi_menu_header'), options = {} } local options = {} for _, v in pairs(config.allowedVehicles) do options[#options + 1] = { title = v.label, event = 'qb-taxi:client:TakeVehicle', args = {model = v.model}, icon = 'fa-solid fa-taxi' } end registeredMenu['options'] = options lib.registerContext(registeredMenu) lib.showContext('garages_depotlist') end local function setupGarageZone() if config.useTarget then lib.requestModel(`a_m_m_indian_01`) taxiPed = CreatePed(3, `a_m_m_indian_01`, 894.93, -179.12, 74.7 - 1.0, 237.09, false, true) SetModelAsNoLongerNeeded(`a_m_m_indian_01`) SetBlockingOfNonTemporaryEvents(taxiPed, true) FreezeEntityPosition(taxiPed, true) SetEntityInvincible(taxiPed, true) exports.ox_target:addLocalEntity(taxiPed, { { type = 'client', event = 'qb-taxijob:client:requestcab', icon = 'fa-solid fa-taxi', label = locale('info.request_taxi_target'), job = 'taxi', } }) else local function onEnter() if not cache.vehicle then lib.showTextUI(locale('info.request_taxi')) end end local function onExit() lib.hideTextUI() end local function inside() if IsControlJustPressed(0, 38) then lib.hideTextUI() taxiGarage() return end end garageZone = lib.zones.box({ coords = config.locations.garage.coords, size = vec3(1.6, 4.0, 2.8), rotation = 328.5, debug = config.debugPoly, inside = inside, onEnter = onEnter, onExit = onExit }) end end local function destroyGarageZone() if not garageZone then return end garageZone:remove() garageZone = nil end function setupTaxiParkingZone() taxiParkingZone = lib.zones.box({ coords = vec3(config.locations.main.coords.x, config.locations.main.coords.y, config.locations.main.coords.z), size = vec3(4.0, 4.0, 4.0), rotation = 55, debug = config.debugPoly, inside = function() if QBX.PlayerData.job.name ~= 'taxi' then return end if IsControlJustPressed(0, 38) then if whitelistedVehicle() then if meterIsOpen then TriggerEvent('qb-taxi:client:toggleMeter') meterActive = false end DeleteVehicle(cache.vehicle) exports.qbx_core:Notify(locale('info.taxi_returned'), 'success') end end end, onEnter = function() lib.showTextUI(locale('info.vehicle_parking')) end, onExit = function() lib.hideTextUI() end }) end local function destroyTaxiParkingZone() if not taxiParkingZone then return end taxiParkingZone:remove() taxiParkingZone = nil end RegisterNetEvent('qb-taxi:client:TakeVehicle', function(data) local SpawnPoint = getVehicleSpawnPoint() if SpawnPoint then local coords = config.cabSpawns[SpawnPoint] local CanSpawn = isSpawnPointClear(coords, 2.0) if CanSpawn then local netId = lib.callback.await('qb-taxi:server:spawnTaxi', false, data.model, coords) local veh = NetToVeh(netId) SetVehicleFuelLevel(veh, 100.0) SetVehicleEngineOn(veh, true, true, false) else exports.qbx_core:Notify(locale('info.no_spawn_point'), 'error') end else exports.qbx_core:Notify(locale('info.no_spawn_point'), 'error') return end end) -- Events RegisterNetEvent('qb-taxi:client:DoTaxiNpc', function() if whitelistedVehicle() then if not NpcData.Active then NpcData.CurrentNpc = math.random(1, #sharedConfig.npcLocations.takeLocations) if NpcData.LastNpc ~= nil then while NpcData.LastNpc ~= NpcData.CurrentNpc do NpcData.CurrentNpc = math.random(1, #sharedConfig.npcLocations.takeLocations) end end local Gender = math.random(1, #config.npcSkins) local PedSkin = math.random(1, #config.npcSkins[Gender]) local model = GetHashKey(config.npcSkins[Gender][PedSkin]) lib.requestModel(model) NpcData.Npc = CreatePed(3, model, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].x, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].y, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].z - 0.98, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].w, true, true) SetModelAsNoLongerNeeded(model) PlaceObjectOnGroundProperly(NpcData.Npc) FreezeEntityPosition(NpcData.Npc, true) if NpcData.NpcBlip ~= nil then RemoveBlip(NpcData.NpcBlip) end exports.qbx_core:Notify(locale('info.npc_on_gps'), 'success') -- added checks to disable distance checking if polyzone option is used if config.useTarget then createNpcPickUpLocation() end NpcData.NpcBlip = AddBlipForCoord(sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].x, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].y, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].z) SetBlipColour(NpcData.NpcBlip, 3) SetBlipRoute(NpcData.NpcBlip, true) SetBlipRouteColour(NpcData.NpcBlip, 3) NpcData.LastNpc = NpcData.CurrentNpc NpcData.Active = true -- added checks to disable distance checking if polyzone option is used if not config.useTarget then CreateThread(function() while not NpcData.NpcTaken do local pos = GetEntityCoords(cache.ped) local dist = #(pos - vec3(sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].x, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].y, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].z)) if dist < 20 then DrawMarker(2, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].x, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].y, sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3, 0.3, 0.3, 255, 255, 255, 255, false, false, 0, true, nil, nil, false) if dist < 5 then qbx.drawText3d({text = locale('info.call_npc'), coords = sharedConfig.npcLocations.takeLocations[NpcData.CurrentNpc].xyz}) if IsControlJustPressed(0, 38) then local maxSeats, freeSeat = GetVehicleMaxNumberOfPassengers(cache.vehicle), 0 for i=maxSeats - 1, 0, -1 do if IsVehicleSeatFree(cache.vehicle, i) then freeSeat = i break end end meterIsOpen = true meterActive = true lastLocation = GetEntityCoords(cache.ped) SendNUIMessage({ action = 'openMeter', toggle = true, meterData = config.meter }) SendNUIMessage({ action = 'toggleMeter' }) ClearPedTasksImmediately(NpcData.Npc) FreezeEntityPosition(NpcData.Npc, false) TaskEnterVehicle(NpcData.Npc, cache.vehicle, -1, freeSeat, 1.0, 0) exports.qbx_core:Notify(locale('info.go_to_location'), 'inform') if NpcData.NpcBlip ~= nil then RemoveBlip(NpcData.NpcBlip) end getDeliveryLocation() NpcData.NpcTaken = true end end end Wait(0) end end) end else exports.qbx_core:Notify(locale('error.already_mission'), 'error') end else exports.qbx_core:Notify(locale('error.not_in_taxi'), 'error') end end) RegisterNetEvent('qb-taxi:client:toggleMeter', function() if cache.vehicle then if whitelistedVehicle() then if not meterIsOpen and isDriver() then SendNUIMessage({ action = 'openMeter', toggle = true, meterData = config.meter }) meterIsOpen = true else SendNUIMessage({ action = 'openMeter', toggle = false }) meterIsOpen = false end else exports.qbx_core:Notify(locale('error.missing_meter'), 'error') end else exports.qbx_core:Notify(locale('error.no_vehicle'), 'error') end end) RegisterNetEvent('qb-taxi:client:enableMeter', function() if meterIsOpen then SendNUIMessage({ action = 'toggleMeter' }) else exports.qbx_core:Notify(locale('error.not_active_meter'), 'error') end end) RegisterNetEvent('qb-taxi:client:toggleMuis', function() Wait(400) if meterIsOpen then if not mouseActive then SetNuiFocus(true, true) mouseActive = true end else exports.qbx_core:Notify(locale('error.no_meter_sight'), 'error') end end) RegisterNetEvent('qb-taxijob:client:requestcab', function() taxiGarage() end) -- NUI Callbacks RegisterNUICallback('enableMeter', function(data, cb) meterActive = data.enabled if not meterActive then resetMeter() end lastLocation = GetEntityCoords(cache.ped) cb('ok') end) RegisterNUICallback('hideMouse', function(_, cb) SetNuiFocus(false, false) mouseActive = false cb('ok') end) -- Threads CreateThread(function() while true do Wait(2000) calculateFareAmount() end end) CreateThread(function() while true do if not cache.vehicle then if meterIsOpen then SendNUIMessage({ action = 'openMeter', toggle = false }) meterIsOpen = false end end Wait(200) end end) local function init() if QBX.PlayerData.job.name == 'taxi' then setupGarageZone() setupTaxiParkingZone() setLocationsBlip() end end RegisterNetEvent('QBCore:Client:OnJobUpdate', function() destroyGarageZone() destroyTaxiParkingZone() init() end) RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() isLoggedIn = true init() end) RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() isLoggedIn = false end) CreateThread(function() if not isLoggedIn then return end init() end)