QB-Taxi - Taxi Service System

The qb-taxi resource provides a comprehensive taxi service system for QBCore servers, featuring ride requests, fare calculation, NPC missions, and complete taxi company operations.

Overview

QB-Taxi creates a realistic taxi service experience with passenger pickup systems, dynamic fare calculation, NPC delivery missions, and taxi company management. Players can work as taxi drivers while others can request rides around the city.

Key Features

  • Ride Request System: Players can call for taxi services
  • Fare Calculation: Distance and time-based pricing
  • NPC Missions: AI passenger delivery jobs
  • Taxi Fleet: Multiple vehicle types and customization
  • GPS Integration: Route planning and navigation
  • Payment Processing: Automated fare collection
  • Driver Rankings: Performance tracking and ratings
  • Company Management: Taxi business operations
  • Emergency Services: Priority rides and medical transport

Installation

Prerequisites

  • QBCore Framework
  • qb-target (for interaction system)
  • qb-menu (for taxi menus)
  • qb-phone (for ride requests)
  • qb-vehiclekeys (for vehicle access)

Installation Steps

  1. Download the Resource
cd resources/[qb]
git clone https://github.com/qbcore-framework/qb-taxi.git
  1. Database Setup
-- Taxi service tables
CREATE TABLE IF NOT EXISTS `taxi_rides` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `driver_citizenid` varchar(50) DEFAULT NULL,
  `passenger_citizenid` varchar(50) DEFAULT NULL,
  `pickup_location` varchar(255) DEFAULT NULL,
  `dropoff_location` varchar(255) DEFAULT NULL,
  `fare` int(11) DEFAULT 0,
  `distance` float DEFAULT 0,
  `duration` int(11) DEFAULT 0,
  `tip` int(11) DEFAULT 0,
  `rating` int(11) DEFAULT 5,
  `created_at` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `taxi_drivers` (
  `citizenid` varchar(50) NOT NULL,
  `total_rides` int(11) DEFAULT 0,
  `total_earnings` int(11) DEFAULT 0,
  `average_rating` float DEFAULT 5.0,
  `status` varchar(50) DEFAULT 'available',
  `current_passenger` varchar(50) DEFAULT NULL,
  `last_active` timestamp DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`citizenid`)
);
 
CREATE TABLE IF NOT EXISTS `taxi_requests` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `passenger_citizenid` varchar(50) DEFAULT NULL,
  `pickup_coords` varchar(255) DEFAULT NULL,
  `dropoff_coords` varchar(255) DEFAULT NULL,
  `status` varchar(50) DEFAULT 'pending',
  `driver_citizenid` varchar(50) DEFAULT NULL,
  `created_at` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
);
  1. Add Job Configuration to qb-core/shared/jobs.lua
['taxi'] = {
    label = 'Taxi Service',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        ['0'] = {
            name = 'Driver',
            payment = 50
        },
        ['1'] = {
            name = 'Experienced Driver',
            payment = 75
        },
        ['2'] = {
            name = 'Senior Driver',
            payment = 100
        },
        ['3'] = {
            name = 'Dispatch Supervisor',
            payment = 125,
            isboss = true
        },
        ['4'] = {
            name = 'Fleet Manager',
            payment = 150,
            isboss = true
        }
    }
}
  1. Add to server.cfg
ensure qb-taxi

Ensure qb-taxi loads after qb-core, qb-target, and qb-phone in your server.cfg

Configuration

Basic Configuration

Config = {}
 
-- General Settings
Config.UseTarget = true
Config.BaseFare = 100        -- Base fare for any ride
Config.PerMeterRate = 2.5    -- Cost per meter traveled
Config.PerMinuteRate = 15    -- Cost per minute in taxi
Config.MaxWaitTime = 300     -- 5 minutes max wait for driver
 
-- Taxi Company Locations
Config.Locations = {
    ["main"] = {
        label = "Downtown Cab Co.",
        coords = vector3(909.49, -177.35, 74.13),
        blip = {
            sprite = 198,
            color = 5,
            scale = 0.8
        },
        zones = {
            duty = vector3(907.24, -177.35, 74.13),
            garage = vector4(905.41, -191.65, 73.53, 235.5),
            boss = vector3(909.49, -177.35, 74.13)
        }
    },
    ["airport"] = {
        label = "Airport Taxi",
        coords = vector3(-1037.15, -2737.48, 20.17),
        zones = {
            duty = vector3(-1037.15, -2737.48, 20.17),
            garage = vector4(-1043.15, -2741.48, 20.17, 330.0)
        }
    }
}
 
-- Taxi Vehicle Configuration
Config.Vehicles = {
    ['taxi'] = {
        label = 'Standard Taxi',
        category = 'taxi',
        spawncost = 0,
        livery = {
            [0] = 'Downtown Cab',
            [1] = 'Taxi & Limousine',
            [2] = 'Yellow Cab'
        }
    },
    ['stretch'] = {
        label = 'Limousine',
        category = 'luxury',
        spawncost = 500,
        multiplier = 2.0 -- 2x fare multiplier
    },
    ['pbus2'] = {
        label = 'Airport Shuttle',
        category = 'shuttle',
        spawncost = 200,
        capacity = 16
    }
}
 
-- NPC Mission Configuration
Config.NPCMissions = {
    enabled = true,
    spawnChance = 0.3, -- 30% chance per check
    checkInterval = 30000, -- 30 seconds
    maxActiveNPCs = 3,
    minPayout = 200,
    maxPayout = 800,
    bonusMultiplier = 1.5 -- Bonus for longer distances
}

Pickup Locations

-- Popular pickup locations for NPC missions
Config.PickupLocations = {
    {coords = vector3(-1037.15, -2737.48, 20.17), label = "Los Santos International Airport"},
    {coords = vector3(240.16, -1378.95, 33.74), label = "Central Medical Center"},
    {coords = vector3(-724.46, -415.49, 35.03), label = "Rockford Plaza"},
    {coords = vector3(425.13, -979.55, 30.71), label = "Police Station"},
    {coords = vector3(-1392.02, -588.61, 30.32), label = "Bahama Mamas"},
    {coords = vector3(1961.95, 3740.5, 32.34), label = "Sandy Shores Airfield"},
    {coords = vector3(-255.20, 6341.36, 32.43), label = "Paleto Bay Medical"},
    {coords = vector3(1836.27, 2584.96, 46.01), label = "Bolingbroke Penitentiary"}
}
 
-- Common dropoff destinations
Config.DropoffLocations = {
    {coords = vector3(72.2, -1392.85, 29.38), label = "Legion Square"},
    {coords = vector3(-1820.20, 794.76, 138.08), label = "Vinewood Hills"},
    {coords = vector3(1207.85, -1447.85, 35.89), label = "Mirror Park"},
    {coords = vector3(-912.81, -364.65, 114.27), label = "West Vinewood"},
    {coords = vector3(1981.85, 3031.85, 47.04), label = "Senora Desert"},
    {coords = vector3(-1174.20, -1571.85, 4.66), label = "Vespucci Beach"},
    {coords = vector3(1126.50, -982.23, 45.42), label = "Mirror Park Boulevard"}
}

API Reference

Client Exports

RequestTaxi

Request a taxi to current location.

-- Basic taxi request
exports['qb-taxi']:RequestTaxi()
 
-- Request with specific dropoff
exports['qb-taxi']:RequestTaxi(dropoffCoords)
 
-- Request luxury taxi
exports['qb-taxi']:RequestTaxi(dropoffCoords, "luxury")
 
-- Parameters:
-- dropoffCoords (optional): Vector3 destination coordinates
-- vehicleType (optional): "standard", "luxury", "shuttle"

CancelTaxiRequest

Cancel pending taxi request.

exports['qb-taxi']:CancelTaxiRequest()

StartMeter

Start the taxi meter for fare calculation.

exports['qb-taxi']:StartMeter(passengerId)
 
-- Parameters:
-- passengerId: Server ID of passenger

StopMeter

Stop the taxi meter and calculate final fare.

local fareInfo = exports['qb-taxi']:StopMeter()
 
-- Returns:
-- {
--   distance = number,
--   time = number,
--   baseFare = number,
--   totalFare = number
-- }

Server Exports

CreateRideRecord

Create a ride record in the database.

local rideId = exports['qb-taxi']:CreateRideRecord(data)
 
-- Parameters:
-- data: {
--   driver_citizenid = string,
--   passenger_citizenid = string,
--   pickup_location = string,
--   dropoff_location = string,
--   fare = number,
--   distance = number,
--   duration = number
-- }

ProcessPayment

Process taxi fare payment.

local success = exports['qb-taxi']:ProcessPayment(passengerId, driverId, amount, tip)
 
-- Parameters:
-- passengerId: Passenger server ID
-- driverId: Driver server ID
-- amount: Fare amount
-- tip: Tip amount (optional)

GetDriverStats

Get driver statistics.

local stats = exports['qb-taxi']:GetDriverStats(citizenId)
 
-- Returns:
-- {
--   total_rides = number,
--   total_earnings = number,
--   average_rating = number,
--   status = string
-- }

Events

Client Events

-- Taxi request accepted
RegisterNetEvent('qb-taxi:client:requestAccepted', function(driverId, driverInfo)
    -- Handle ride acceptance
end)
 
-- Driver arrived at pickup
RegisterNetEvent('qb-taxi:client:driverArrived', function()
    -- Handle driver arrival
end)
 
-- Ride started
RegisterNetEvent('qb-taxi:client:rideStarted', function(destination)
    -- Handle ride start
end)
 
-- Ride completed
RegisterNetEvent('qb-taxi:client:rideCompleted', function(fareInfo)
    -- Handle ride completion
end)

Server Events

-- New taxi request
RegisterNetEvent('qb-taxi:server:newRequest', function(requestData)
    -- Handle new ride request
end)
 
-- Accept ride request
RegisterNetEvent('qb-taxi:server:acceptRequest', function(requestId)
    -- Handle request acceptance
end)
 
-- Ride completion
RegisterNetEvent('qb-taxi:server:completeRide', function(rideData)
    -- Handle ride completion
end)
 
-- Rate driver
RegisterNetEvent('qb-taxi:server:rateDriver', function(driverId, rating)
    -- Handle driver rating
end)

Usage Examples

Taxi Request System

-- Client-side taxi calling
RegisterCommand('taxi', function()
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    
    -- Check if player is already in a taxi or has pending request
    QBCore.Functions.TriggerCallback('qb-taxi:server:hasActiveRequest', function(hasRequest)
        if hasRequest then
            QBCore.Functions.Notify("You already have an active taxi request", "error")
            return
        end
        
        -- Create request menu
        local requestMenu = {
            {
                header = "Taxi Service",
                isMenuHeader = true
            },
            {
                header = "Standard Taxi",
                txt = "Base fare: $" .. Config.BaseFare,
                params = {
                    event = "qb-taxi:client:requestTaxi",
                    args = {type = "standard"}
                }
            },
            {
                header = "Luxury Taxi",
                txt = "Premium service (2x fare)",
                params = {
                    event = "qb-taxi:client:requestTaxi", 
                    args = {type = "luxury"}
                }
            },
            {
                header = "Airport Shuttle",
                txt = "Shared ride service",
                params = {
                    event = "qb-taxi:client:requestTaxi",
                    args = {type = "shuttle"}
                }
            }
        }
        
        exports['qb-menu']:openMenu(requestMenu)
    end)
end)
 
-- Handle taxi request
RegisterNetEvent('qb-taxi:client:requestTaxi', function(data)
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local streetHash, crossingHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z)
    local streetName = GetStreetNameFromHashKey(streetHash)
    
    TriggerServerEvent('qb-taxi:server:createRequest', {
        pickup_coords = coords,
        pickup_location = streetName,
        vehicle_type = data.type
    })
    
    QBCore.Functions.Notify("Taxi request sent! Looking for available drivers...", "success")
end)

Driver-Side Operations

-- Accept taxi request
RegisterNetEvent('qb-taxi:client:acceptRequest', function(requestData)
    local player = QBCore.Functions.GetPlayerData()
    if player.job.name == "taxi" and player.job.onduty then
        -- Set GPS waypoint to pickup location
        SetNewWaypoint(requestData.pickup_coords.x, requestData.pickup_coords.y)
        
        QBCore.Functions.Notify("Request accepted! Navigate to pickup location", "success")
        
        -- Create pickup blip
        local pickupBlip = AddBlipForCoord(requestData.pickup_coords.x, requestData.pickup_coords.y, requestData.pickup_coords.z)
        SetBlipSprite(pickupBlip, 280)
        SetBlipColour(pickupBlip, 5)
        SetBlipRoute(pickupBlip, true)
        SetBlipAsShortRange(pickupBlip, false)
        BeginTextCommandSetBlipName("STRING")
        AddTextComponentString("Taxi Pickup")
        EndTextCommandSetBlipName(pickupBlip)
        
        -- Store request data
        currentRequest = requestData
        currentRequest.pickupBlip = pickupBlip
    end
end)
 
-- Start ride with passenger
RegisterNetEvent('qb-taxi:client:startRide', function(passengerId)
    local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
    
    if vehicle and GetPedInVehicleSeat(vehicle, 0) == PlayerPedId() then
        -- Start fare meter
        exports['qb-taxi']:StartMeter(passengerId)
        
        -- Remove pickup blip
        if currentRequest and currentRequest.pickupBlip then
            RemoveBlip(currentRequest.pickupBlip)
        end
        
        -- Create destination blip if provided
        if currentRequest and currentRequest.dropoff_coords then
            local destBlip = AddBlipForCoord(currentRequest.dropoff_coords.x, currentRequest.dropoff_coords.y, currentRequest.dropoff_coords.z)
            SetBlipSprite(destBlip, 162)
            SetBlipColour(destBlip, 2)
            SetBlipRoute(destBlip, true)
            BeginTextCommandSetBlipName("STRING")
            AddTextComponentString("Taxi Destination")
            EndTextCommandSetBlipName(destBlip)
            currentRequest.destBlip = destBlip
        end
        
        QBCore.Functions.Notify("Ride started! Meter is running", "success")
    end
end)

NPC Mission System

-- Server-side NPC mission spawning
CreateThread(function()
    while true do
        Wait(Config.NPCMissions.checkInterval)
        
        if Config.NPCMissions.enabled then
            -- Get all active taxi drivers
            local activeTaxis = {}
            for k, v in pairs(QBCore.Functions.GetQBPlayers()) do
                if v.PlayerData.job.name == "taxi" and v.PlayerData.job.onduty then
                    table.insert(activeTaxis, v.PlayerData.source)
                end
            end
            
            -- Spawn NPC missions for active drivers
            for i = 1, #activeTaxis do
                if math.random() < Config.NPCMissions.spawnChance then
                    local driverId = activeTaxis[i]
                    CreateNPCMission(driverId)
                end
            end
        end
    end
end)
 
function CreateNPCMission(driverId)
    local pickup = Config.PickupLocations[math.random(1, #Config.PickupLocations)]
    local dropoff = Config.DropoffLocations[math.random(1, #Config.DropoffLocations)]
    local distance = #(pickup.coords - dropoff.coords)
    local basePayout = math.random(Config.NPCMissions.minPayout, Config.NPCMissions.maxPayout)
    
    -- Calculate bonus for longer distances
    local payout = basePayout
    if distance > 1000 then
        payout = math.floor(payout * Config.NPCMissions.bonusMultiplier)
    end
    
    local missionData = {
        id = math.random(10000, 99999),
        pickup = pickup,
        dropoff = dropoff,
        distance = distance,
        payout = payout,
        type = "npc"
    }
    
    TriggerClientEvent('qb-taxi:client:npcMission', driverId, missionData)
end

Fare Calculation System

-- Calculate fare based on distance and time
function CalculateFare(distance, timeInSeconds, vehicleType)
    local baseFare = Config.BaseFare
    local distanceFare = distance * Config.PerMeterRate
    local timeFare = (timeInSeconds / 60) * Config.PerMinuteRate
    
    local totalFare = baseFare + distanceFare + timeFare
    
    -- Apply vehicle type multiplier
    if vehicleType == "luxury" then
        totalFare = totalFare * 2.0
    elseif vehicleType == "shuttle" then
        totalFare = totalFare * 0.8 -- 20% discount for shared rides
    end
    
    return math.floor(totalFare)
end
 
-- Client-side meter system
local meterActive = false
local meterStartTime = 0
local meterStartCoords = nil
 
RegisterNetEvent('qb-taxi:client:startMeter', function(passengerId)
    if not meterActive then
        meterActive = true
        meterStartTime = GetGameTimer()
        meterStartCoords = GetEntityCoords(PlayerPedId())
        
        QBCore.Functions.Notify("Fare meter started", "success")
        
        -- Start meter display thread
        CreateThread(function()
            while meterActive do
                Wait(1000)
                local currentTime = GetGameTimer()
                local elapsedSeconds = (currentTime - meterStartTime) / 1000
                local currentCoords = GetEntityCoords(PlayerPedId())
                local distance = #(meterStartCoords - currentCoords)
                local currentFare = CalculateFare(distance, elapsedSeconds, "standard")
                
                -- Display current fare on screen
                SetTextComponentFormat("STRING")
                AddTextComponentString("~g~Fare: ~w~$" .. currentFare .. " | ~b~Time: ~w~" .. math.floor(elapsedSeconds / 60) .. ":" .. string.format("%02d", elapsedSeconds % 60))
                DisplayText(0.01, 0.01)
            end
        end)
    end
end)

Troubleshooting

Common Issues

Taxi Requests Not Working

-- Check if phone resource is running
if not GetResourceState('qb-phone') == 'started' then
    print("qb-phone is required for taxi requests")
end
 
-- Verify database tables exist
QBCore.Functions.ExecuteSql(false, "SHOW TABLES LIKE 'taxi_requests'")

Fare Calculation Issues

  • Verify distance calculation using proper coordinate systems
  • Check if meter is properly started and stopped
  • Ensure vehicle type multipliers are configured correctly

NPC Missions Not Spawning

-- Debug NPC mission system
RegisterCommand('debugnpc', function()
    local player = QBCore.Functions.GetPlayerData()
    if player.job.name == "taxi" then
        CreateNPCMission(GetPlayerServerId(PlayerId()))
    end
end)

Debug Commands

-- Force taxi request (admin only)
/forcetaxi [player_id]
 
-- Complete current ride
/completeride
 
-- Check driver stats
/taxistats [driver_name]
 
-- Toggle meter manually
/meter

Performance Optimization

  1. Request Cleanup: Remove expired taxi requests from database
  2. Blip Management: Clean up unused blips and waypoints
  3. NPC Spawning: Limit concurrent NPC missions per driver
⚠️

Test the fare calculation system thoroughly to ensure accurate pricing and prevent exploitation.