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
- Download the Resource
cd resources/[qb]
git clone https://github.com/qbcore-framework/qb-taxi.git
- 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`)
);
- 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
}
}
}
- 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
- Request Cleanup: Remove expired taxi requests from database
- Blip Management: Clean up unused blips and waypoints
- NPC Spawning: Limit concurrent NPC missions per driver
Test the fare calculation system thoroughly to ensure accurate pricing and prevent exploitation.