Система такси для FiveM: скрипты для NPC и игроков-водителей такси
Добавьте работающую систему такси в FiveM. NPC такси, водители-игроки, логика тарифа, счётчики и лучшие скрипты такси для оживлённых ролевых городов.
Agency Scripts
Основатель и ведущий разработчик Agency Scripts
Архитектура системы работы такси
Система работы таксиста , ключевая функция для любого ролевого сервера, желающего предоставить игрокам доступную начальную работу с настоящим взаимодействием между игроками. В отличие от многих других профессий, которые могут казаться однообразными и утомительными, хорошо продуманная система такси создаёт органичные RP-ситуации, когда водители подбирают пассажиров, договариваются о цене и ориентируются по городу под давлением времени. Архитектура делится на два режима: транспортировка игрок-игрок, где реальные пассажиры заказывают поездки, и режим миссий с NPC, где система генерирует точки посадки и высадки для одиночной игры в непиковые часы. Оба режима используют один и тот же движок расчёта стоимости, интерфейс таксометра и систему управления транспортом. Ключевое решение , использовать централизованную диспетчерскую систему с общей очередью заявок, видимой всем дежурным водителям, или систему на основе близости, где приоритет получает ближайший водитель. Большинство успешных реализаций используют гибридный подход с диспетчерской доской, показывающей все заявки, но выделяющей ближайшие, давая водителям свободу выбора и обеспечивая быструю посадку пассажиров.
Реализация таксометра
Таксометр , сердце системы тарификации, он должен выглядеть аутентично и оставаться прозрачным для водителя и пассажира. Правильный таксометр отслеживает две составляющие: базовый тариф, взимаемый в начале каждой поездки, и тариф за пройденное расстояние, который увеличивается по мере движения автомобиля. Некоторые серверы также добавляют тариф за время ожидания, взимающий меньшую плату за секунду, когда такси стоит на месте, имитируя реальную практику оплаты времени в пробках. Таксометр должен быть виден и водителю, и пассажирам через постоянный элемент NUI, расположенный в углу экрана. Вот реализация таксометра на стороне клиента, который рассчитывает стоимость поездки по пройденному расстоянию:
local meterActive = false
local currentFare = 0.0
local lastPosition = nil
local baseFare = 50
local ratePerMile = 15
local waitRatePerSecond = 0.5
local totalDistance = 0.0
function StartMeter()
meterActive = true
currentFare = baseFare
totalDistance = 0.0
lastPosition = GetEntityCoords(PlayerPedId())
SendNUIMessage({action = 'showMeter', fare = currentFare, distance = 0.0})
CreateThread(function()
while meterActive do
Wait(500)
local currentPos = GetEntityCoords(PlayerPedId())
local speed = GetEntitySpeed(PlayerPedId())
if speed > 1.0 then
local dist = #(currentPos - lastPosition)
totalDistance = totalDistance + dist
local miles = totalDistance / 1609.34
currentFare = baseFare + (miles * ratePerMile)
else
currentFare = currentFare + (waitRatePerSecond * 0.5)
end
lastPosition = currentPos
SendNUIMessage({
action = 'updateMeter',
fare = math.floor(currentFare),
distance = math.floor(totalDistance),
speed = math.floor(speed * 2.236936)
})
end
end)
end
function StopMeter()
meterActive = false
local finalFare = math.floor(currentFare)
SendNUIMessage({action = 'stopMeter', fare = finalFare})
return finalFare
end
Компонент NUI для счётчика должен быть компактным, но читаемым, показывая текущий тариф крупно вместе с пройденным расстоянием и применяемым тарифом. Используйте моноширинный шрифт для отображения тарифа, чтобы имитировать вид настоящего таксометра, и добавьте тонкую анимацию тика при каждом увеличении тарифа, чтобы дать визуальную обратную связь о работе счётчика. Рассмотрите возможность добавления функции оценки стоимости, которая рассчитывает приблизительную цену на основе прямой дистанции до пункта назначения до начала поездки, чтобы пассажиры могли решить, хотят ли они продолжать.
Система миссий с NPC для посадки
Миссии с NPC заняты таксистов, когда на сервере недостаточно реальных пассажиров. Система генерирует случайные точки посадки и высадки из тщательно подобранного списка интересных мест по карте, создаёт NPC в точке посадки и ведёт водителя через полный цикл поездки. Качество миссии с NPC сильно зависит от данных о локациях: использование только случайных координат приводит к неестественным посадкам в глуши, поэтому поддержание вручную подобранного списка реалистичных мест, таких как больницы, рестораны, отели и торговые центры, делает миссии правдоподобными. Вот серверная логика для генерации и отправки миссий с NPC:
local MissionLocations = {
{coords = vector3(-240.49, -987.78, 29.29), label = 'Pillbox Medical Center'},
{coords = vector3(228.28, -790.70, 30.58), label = 'Legion Square'},
{coords = vector3(-1044.57, -2739.76, 13.85), label = 'Los Santos Airport'},
{coords = vector3(126.19, -1282.72, 29.27), label = 'Strawberry Avenue'},
{coords = vector3(-556.18, -196.17, 38.22), label = 'Rockford Hills'},
{coords = vector3(1159.56, -314.45, 69.21), label = 'Vinewood Hills'},
{coords = vector3(-1222.08, -906.82, 12.32), label = 'Del Perro Pier'},
{coords = vector3(373.58, 325.71, 103.57), label = 'Downtown Vinewood'},
}
RegisterNetEvent('taxi:server:requestNPCMission', function()
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player or Player.PlayerData.job.name ~= 'taxi' then return end
local pickup = MissionLocations[math.random(#MissionLocations)]
local dropoff = MissionLocations[math.random(#MissionLocations)]
while pickup.label == dropoff.label do
dropoff = MissionLocations[math.random(#MissionLocations)]
end
local estimatedDistance = #(pickup.coords - dropoff.coords)
local basePay = 50
local distancePay = math.floor(estimatedDistance / 1609.34 * 15)
local totalPay = basePay + distancePay
TriggerClientEvent('taxi:client:startNPCMission', src, {
pickup = pickup,
dropoff = dropoff,
estimatedPay = totalPay,
npcModel = Config.NPCModels[math.random(#Config.NPCModels)],
missionId = 'TAXI-' .. os.time() .. '-' .. math.random(1000, 9999)
})
end)
На клиентской стороне создайте NPC в точке выдачи с анимацией ожидания, добавьте блип для навигации водителя и воспроизведите анимацию посадки при прибытии водителя. Во время поездки NPC должен демонстрировать естественное поведение: осматриваться и время от времени комментировать в чат-баблах. По прибытии на место NPC выходит, а оплата автоматически производится через серверное событие. Добавьте разнообразия, периодически генерируя специальные миссии с повышенной оплатой, например, трансферы в аэропорт или VIP-клиентов, требующих аккуратного вождения без столкновений.
Расчёт стоимости проезда и повышенные тарифы
Помимо базового расчёта на основе расстояния, продвинутая система тарификации учитывает время суток, дорожные условия и тип транспортного средства. Реализуйте повышенные тарифы в часы пик на сервере, когда онлайн высокий и спрос на такси превышает предложение. Множитель повышения тарифа должен быть прозрачно сообщён пассажирам до подтверждения поездки, чтобы избежать сюрпризов в конце. Вы можете динамически рассчитывать спрос, отслеживая соотношение ожидающих запросов на поездку к доступным водителям. Вот движок расчёта тарифа, который учитывает эти переменные:
function CalculateFare(pickupCoords, dropoffCoords, vehicleClass)
local distance = #(pickupCoords - dropoffCoords)
local miles = distance / 1609.34
local rates = Config.FareRates
local baseFare = rates.base
local perMile = rates.perMile
-- Vehicle class multiplier
local classMultiplier = {
[0] = 0.8, -- Compacts (budget)
[1] = 1.0, -- Sedans (standard)
[2] = 1.0, -- SUVs
[3] = 1.2, -- Coupes (comfort)
[18] = 1.5, -- Super (luxury)
}
local vehMultiplier = classMultiplier[vehicleClass] or 1.0
-- Time-of-day multiplier (night premium)
local hour = GetClockHours()
local timeMultiplier = 1.0
if hour >= 22 or hour <= 5 then
timeMultiplier = 1.3 -- 30% night surcharge
end
-- Surge pricing based on demand
local surgeMultiplier = GetCurrentSurgeMultiplier()
local totalFare = (baseFare + (miles * perMile)) * vehMultiplier * timeMultiplier * surgeMultiplier
return {
total = math.floor(totalFare),
breakdown = {
base = baseFare,
distance = math.floor(miles * perMile),
vehicleClass = vehMultiplier,
nightSurcharge = timeMultiplier > 1.0,
surgeActive = surgeMultiplier > 1.0,
surgeRate = surgeMultiplier
}
}
end
Отображайте разбивку стоимости проезда пассажиру при запросе поездки, чтобы он понимал, за что платит. Если активировано повышение цен (surge pricing), четко показывайте множитель другим цветом в интерфейсе. Такая прозрачность укрепляет доверие между водителями и пассажирами и снижает споры по суммам оплаты. Позвольте пассажирам выбирать предпочитаемый класс автомобиля при бронировании: бюджетные варианты , компактные машины, а люксовые , премиальные седаны или внедорожники с повышенными тарифами.
Отображение маршрутов и GPS-навигация
Визуальное руководство по маршруту делает опыт таксиста более плавным как для новичков, так и для опытных водителей. FiveM предоставляет SetNewWaypoint native для базовой маршрутизации GPS, но более отточенный подход рисует маршрут прямо на миникарте и опционально отображает 3D путь в мире. Используйте SetBlipRoute функция на метках назначения для включения встроенного GPS-маршрута и улучшения его пользовательскими метками на ключевых путевых точках. Для удобства пассажира отображайте предполагаемое время прибытия и оставшееся расстояние в UI счетчика, обновляя в реальном времени по мере движения транспортного средства. Вот как настроить систему отображения маршрута с отслеживанием путевых точек:
local routeBlip = nil
local checkpointBlips = {}
function SetTaxiRoute(destination, checkpoints)
ClearTaxiRoute()
routeBlip = AddBlipForCoord(destination.x, destination.y, destination.z)
SetBlipSprite(routeBlip, 198)
SetBlipColour(routeBlip, 5)
SetBlipScale(routeBlip, 1.0)
SetBlipRoute(routeBlip, true)
SetBlipRouteColour(routeBlip, 5)
BeginTextCommandSetBlipName('STRING')
AddTextComponentSubstringPlayerName('Destination')
EndTextCommandSetBlipName(routeBlip)
if checkpoints then
for i, cp in ipairs(checkpoints) do
local cpBlip = AddBlipForCoord(cp.x, cp.y, cp.z)
SetBlipSprite(cpBlip, 1)
SetBlipColour(cpBlip, 3)
SetBlipScale(cpBlip, 0.6)
table.insert(checkpointBlips, cpBlip)
end
end
end
function ClearTaxiRoute()
if routeBlip then
SetBlipRoute(routeBlip, false)
RemoveBlip(routeBlip)
routeBlip = nil
end
for _, blip in ipairs(checkpointBlips) do
RemoveBlip(blip)
end
checkpointBlips = {}
end
function UpdateETADisplay(destination)
CreateThread(function()
while routeBlip do
Wait(2000)
local pos = GetEntityCoords(PlayerPedId())
local remaining = #(pos - destination)
local speed = GetEntitySpeed(PlayerPedId())
local etaSeconds = speed > 2.0 and (remaining / speed) or 0
local etaMinutes = math.floor(etaSeconds / 60)
SendNUIMessage({
action = 'updateETA',
distance = math.floor(remaining),
eta = etaMinutes > 0 and (etaMinutes .. ' min') or 'Arriving'
})
end
end)
end
Для дополнительного погружения реализуйте систему рейтинга, где пассажиры оценивают поездку по таким параметрам, как время в пути относительно оценки, количество столкновений во время поездки и соблюдение водителем правил дорожного движения. Отображайте средний рейтинг водителя в его профиле такси, чтобы пассажиры видели его перед принятием заказа, создавая стимул для водителей предоставлять качественный сервис.
Система чаевых и рейтинги водителей
Система чаевых добавляет социальный элемент, поощряющий водителей предоставлять отличный сервис. После оплаты поездки и выхода пассажира отображайте запрос на чаевые с предустановленными вариантами: 10%, 15%, 20% или произвольная сумма. Чаевые идут напрямую водителю поверх базового тарифа и комиссии таксопарка. Отслеживайте суммарные чаевые каждого водителя в базе данных для создания таблиц лидеров и разблокировки бонусов для лучших водителей. На сервере обрабатывайте чаевые через ту же систему транзакций, что и тарифы, чтобы обеспечить согласованность и избежать дублирования. Водители с высоким рейтингом и средним размером чаевых могут получить бонусы: доступ к премиум-транспорту, сниженные комиссии или приоритет в очереди диспетчера. Совместите систему чаевых с системой отзывов, где пассажиры оставляют краткий текстовый отзыв, видимый водителю, создавая обратную связь, улучшающую качество такси на сервере.
Панель диспетчера и заявки на поездки
Диспетчерская доска , центральный узел, где таксисты находят следующую работу. Создайте её как NUI-панель, доступную из штаба таксопарка или через горячую клавишу на дежурстве. Доска должна отображать все ожидающие заявки, отсортированные по близости, с каждой записью, показывающей имя пассажира, место посадки, пункт назначения, предполагаемый тариф и расстояние. Когда водитель принимает заказ, удаляйте его с доски для всех остальных водителей и запускайте таймер обратного отсчёта; если водитель не прибывает к месту посадки в отведённое время, заказ возвращается на доску, а водителю начисляется штраф за надёжность. Для пассажиров создайте простое приложение телефона или команду, позволяющую запросить поездку, указав текущее местоположение и пункт назначения. Запрос попадает в очередь диспетчеризации, и пассажир получает обновления по мере принятия и приближения водителя. Реализуйте систему отмены с небольшой платой за поздние отмены, чтобы предотвратить повторные запросы и отмены, которые тратят время водителей и создают неприятный опыт.
Управление транспортом и система автопарка
Полная система такси требует правильного управления транспортом, чтобы водители использовали подходящие машины, а компания поддерживала свой автопарк. Создайте систему выдачи автомобилей на таксопарке, где водители выбирают из доступных машин компании. Каждая машина должна иметь учёт пробега, уровень топлива и состояние, а водители отвечают за возврат автомобилей в приемлемом состоянии или сталкиваются с вычетами на ремонт из заработка. В парке должны быть разные классы машин: экономичные седаны для стандартных поездок, комфортные внедорожники для премиум-сервиса и люксовые автомобили для VIP-заказов. Отслеживайте статистику использования каждого автомобиля, включая количество выполненных заказов, выручку и затраты на обслуживание. Реализуйте систему расхода топлива, требующую заправки на АЗС во время смены, добавляя реалистичные операционные расходы, которые опытные водители научатся эффективно контролировать. В конце смены водитель возвращает машину на базу, рассчитывается общий заработок за вычетом комиссии компании и затрат на повреждения, а чистая сумма зачисляется на банковский счёт. Этот полный цикл создаёт реалистичный опыт работы, отражающий реальные операции таксомоторных компаний.