Система эвакуатора для FiveM: скрипты профессиональных эвакуационных работ
Запускайте работу эвакуатора в FiveM. Запросы NPC, логика конфискации, анимации платформы и лучшие скрипты эвакуатора для интеграции с механиками и полицией.
Agency Scripts
Основатель и ведущий разработчик Agency Scripts
Обзор системы эвакуаторов
Система эвакуатора и штрафстоянки создаёт целую экосистему работы на вашем FiveM сервере, предоставляя игрокам гражданскую карьеру с естественным взаимодействием с полицией, механиками и другими водителями. Система включает несколько взаимосвязанных компонентов: управление сменами с учётом входа и выхода, диспетчерскую систему, направляющую заявки на эвакуацию доступным водителям, механику крепления транспорта для реалистичного буксирования, штрафстоянку для хранения изъятых и брошенных машин и структуру оплаты за возврат. При правильном дизайне работа эвакуаторщика становится одной из самых социальных ролей на сервере, так как каждый вызов требует прямого взаимодействия с другим игроком, которому нужна помощь с поломанным или неправильно припаркованным транспортом.
Настройка работы и система дежурств
Начните с определения работы эвакуатора в системе работ вашего Framework. Игроки должны иметь возможность отмечаться на смену на площадке эвакуатора, получать служебный эвакуатор и начинать принимать вызовы диспетчера. Система дежурств должна отслеживать активных водителей эвакуаторов, чтобы диспетчерская система знала, кто доступен. Когда игрок отмечается на смену, создавайте эвакуатор с плоской платформой из заранее определённого списка моделей эвакуаторов и назначайте его этому игроку. При выходе с работы служебный транспорт должен быть возвращён или удалён. Храните статус дежурства игрока на стороне сервера, чтобы он корректно сохранялся и мог запрашиваться диспетчерской системой:
Config.TowJob = {
jobName = 'tow',
clockInLocation = vector3(409.09, -1622.57, 29.29),
vehicleSpawn = vector4(405.87, -1631.14, 29.29, 228.35),
towVehicles = {
{model = 'flatbed', label = 'Flatbed Truck', rank = 0},
{model = 'towtruck', label = 'Tow Truck (Hook)', rank = 1},
{model = 'towtruck2', label = 'Heavy Tow Truck', rank = 2},
},
impoundLot = vector3(409.59, -1637.87, 29.29),
impoundReturn = vector4(403.12, -1640.23, 29.29, 138.92),
payPerTow = {min = 350, max = 750},
payPerImpound = 500,
}
RegisterNetEvent('tow:server:clockIn', function()
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
if Player.PlayerData.job.name ~= Config.TowJob.jobName then
TriggerClientEvent('QBCore:Notify', src, 'You are not a tow driver', 'error')
return
end
Player.Functions.SetJobDuty(true)
TowDrivers[src] = {
onDuty = true,
currentCall = nil,
vehicleNet = nil,
towedVehicle = nil,
}
TriggerClientEvent('QBCore:Notify', src, 'You are now on duty', 'success')
TriggerClientEvent('tow:client:spawnVehicle', src)
end)
Механика крепления транспортных средств
Основная техническая задача системы эвакуации , это крепление одного транспортного средства к другому так, чтобы это выглядело реалистично и корректно вело себя при движении. FiveM предоставляет AttachEntityToEntity native для этой цели, но правильная настройка смещений требует тщательной калибровки для каждой модели эвакуатора. Плоская платформа работает лучше всего для большинства автомобилей: буксируемый автомобиль размещается на платформе с соответствующими смещениями, чтобы он естественно сидел на платформе. Для эвакуаторов с крюком передние колёса буксируемого автомобиля поднимаются, а задние остаются на земле. Каждый стиль крепления требует разных векторов смещения и значений поворота. Тестируйте эти значения в игре, делая небольшие корректировки, пока автомобиль не сядет правильно без пересечений с эвакуатором:
function AttachVehicleToFlatbed(towTruck, targetVehicle)
local towModel = GetEntityModel(towTruck)
-- Offsets calibrated for the flatbed model
local offsets = {
['flatbed'] = {x = 0.0, y = -0.5, z = 1.1, rx = 0.0, ry = 0.0, rz = 0.0},
}
local offset = offsets['flatbed']
if not offset then return false end
-- Freeze target vehicle physics
SetEntityCollision(targetVehicle, false, false)
FreezeEntityPosition(targetVehicle, true)
-- Attach to flatbed
AttachEntityToEntity(
targetVehicle, towTruck,
0, -- bone index
offset.x, offset.y, offset.z,
offset.rx, offset.ry, offset.rz,
false, false, false, false, 0, true
)
-- Store attachment state
TriggerServerEvent('tow:server:vehicleAttached', VehToNet(towTruck), VehToNet(targetVehicle))
return true
end
function DetachVehicleFromFlatbed(towTruck, targetVehicle)
DetachEntity(targetVehicle, true, true)
SetEntityCollision(targetVehicle, true, true)
FreezeEntityPosition(targetVehicle, false)
-- Place vehicle on ground behind the truck
local truckCoords = GetEntityCoords(towTruck)
local truckHeading = GetEntityHeading(towTruck)
local behind = GetOffsetFromEntityInWorldCoords(towTruck, 0.0, -12.0, 0.0)
SetEntityCoords(targetVehicle, behind.x, behind.y, behind.z)
SetEntityHeading(targetVehicle, truckHeading)
SetVehicleOnGroundProperly(targetVehicle)
TriggerServerEvent('tow:server:vehicleDetached', VehToNet(towTruck))
end
Важной деталью является отключение коллизии на прикрепленном транспортном средстве для предотвращения сбоев физики. Когда два транспортных средства имеют активную коллизию и прикреплены друг к другу, игровой движок может вызвать сильную тряску или выбросить транспортные средства в воздух. Заморозка позиции целевого транспортного средства и отключение его коллизии во время прикрепления полностью устраняет эти проблемы.
Система диспетчеризации и запроса эвакуатора
Система диспетчеризации соединяет игроков, нуждающихся в эвакуации, с доступными водителями эвакуаторов. Игроки могут запросить эвакуацию через телефон, NPC в автомастерской или через уличные колонки вызова, размещённые по карте. Когда поступает запрос, сервер проверяет доступных водителей и направляет вызов ближайшему. Если водителей нет на дежурстве, предоставляется запасной NPC-сервис эвакуации с более высокой платой и телепортацией автомобиля на штрафстоянку с задержкой. Уведомление диспетчера должно включать местоположение заявителя, модель автомобиля и причину эвакуации, например поломка, авария или незаконная парковка. Водителю эвакуатора предоставляется возможность принять или отклонить вызов; отклонённые вызовы перенаправляются следующему ближайшему водителю:
RegisterNetEvent('tow:server:requestTow', function(reason)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local playerCoords = GetEntityCoords(GetPlayerPed(src))
local vehicle = GetVehiclePedIsIn(GetPlayerPed(src), true)
local vehicleModel = vehicle ~= 0 and GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) or 'Unknown'
-- Find nearest available tow driver
local nearestDriver = nil
local nearestDist = math.huge
for driverId, data in pairs(TowDrivers) do
if data.onDuty and not data.currentCall then
local driverCoords = GetEntityCoords(GetPlayerPed(driverId))
local dist = #(playerCoords - driverCoords)
if dist < nearestDist then
nearestDist = dist
nearestDriver = driverId
end
end
end
if nearestDriver then
TowDrivers[nearestDriver].currentCall = {
requesterId = src,
coords = playerCoords,
vehicle = vehicleModel,
reason = reason,
timestamp = os.time(),
}
TriggerClientEvent('tow:client:incomingCall', nearestDriver, {
coords = playerCoords,
vehicle = vehicleModel,
reason = reason,
playerName = Player.PlayerData.charinfo.firstname,
})
TriggerClientEvent('QBCore:Notify', src, 'Tow truck dispatched. ETA based on driver location.', 'success')
else
TriggerClientEvent('QBCore:Notify', src, 'No tow drivers available. NPC service will arrive shortly.', 'info')
StartNPCTowService(src, playerCoords, vehicle)
end
end)
Управление штрафстоянкой
Стоянка для эвакуированных и изъятых транспортных средств , это место их хранения до тех пор, пока владельцы не заберут их. Спроектируйте базу данных стоянки так, чтобы отслеживать, какие транспортные средства в данный момент находятся на стоянке, кто их эвакуировал, когда они были эвакуированы и какую плату нужно заплатить за их возврат. Полиция должна иметь возможность эвакуировать транспортные средства непосредственно во время остановок или после погонь, при этом такие эвакуации сопровождаются более высокими сборами или более длительным обязательным сроком хранения. Интерфейс стоянки должен отображать список эвакуированных транспортных средств игрока с деталями, такими как модель, номерной знак, дата эвакуации, причина и плата за возврат. Игроки оплачивают сбор на стойке стоянки, и их транспортное средство появляется в назначенной точке возврата рядом:
CREATE TABLE IF NOT EXISTS impounded_vehicles (
id INT AUTO_INCREMENT PRIMARY KEY,
plate VARCHAR(10) NOT NULL,
citizenid VARCHAR(50) NOT NULL,
vehicle_data LONGTEXT NOT NULL,
impound_reason ENUM('tow', 'police', 'abandoned', 'parking') DEFAULT 'tow',
impound_fee INT DEFAULT 250,
impounded_by VARCHAR(50) DEFAULT NULL,
impounded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
hold_until TIMESTAMP DEFAULT NULL,
released_at TIMESTAMP DEFAULT NULL,
INDEX idx_plate (plate),
INDEX idx_citizen (citizenid),
INDEX idx_released (released_at)
);
-- Server-side: Impound a vehicle
RegisterNetEvent('tow:server:impoundVehicle', function(vehicleNet, reason)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local vehicle = NetworkGetEntityFromNetworkId(vehicleNet)
if not DoesEntityExist(vehicle) then return end
local plate = GetVehicleNumberPlateText(vehicle)
local vehicleData = QBCore.Functions.GetVehicleProperties(vehicle)
local fee = Config.ImpoundFees[reason] or 250
MySQL.insert('INSERT INTO impounded_vehicles (plate, citizenid, vehicle_data, impound_reason, impound_fee, impounded_by) VALUES (?, ?, ?, ?, ?, ?)', {
plate:trim(),
GetVehicleOwner(plate),
json.encode(vehicleData),
reason,
fee,
Player.PlayerData.citizenid,
})
DeleteEntity(vehicle)
local pay = math.random(Config.TowJob.payPerImpound - 100, Config.TowJob.payPerImpound + 100)
Player.Functions.AddMoney('bank', pay, 'tow-impound')
TriggerClientEvent('QBCore:Notify', src, 'Vehicle impounded. Earned $' .. pay, 'success')
end)
Автоматическое обеспечение парковки
Добавьте автоматическую систему, которая обнаруживает автомобили, припаркованные в запрещённых зонах, и помечает их для эвакуации после настраиваемого льготного периода. Определите зоны запрещённой парковки вокруг пожарных гидрантов, автобусных остановок, полицейских участков и входов в отделения скорой помощи. Когда игрок паркуется в запрещённой зоне и уходит, запускается таймер. Если транспортное средство остаётся без присмотра после истечения льготного периода, создаётся запрос на эвакуацию и уведомляются доступные водители эвакуаторов. Это создаёт естественную работу для водителей эвакуаторов без необходимости вручную вызывать службу. Система должна помечать только транспорт, принадлежащий игрокам, поскольку NPC-автомобили управляются игровым движком и исчезают естественным образом. Включите уведомление владельцу автомобиля о том, что его машина помечена для эвакуации, давая шанс вернуться и убрать её до прибытия эвакуатора.
Доход и прогресс
Структурируйте работу эвакуатора с системой прогрессии, которая вознаграждает постоянную работу. Новые водители эвакуаторов начинают с нулевого ранга с доступом к базовому эвакуатору с плоской платформой и получают базовую ставку за каждый выполненный вызов. По мере накопления выполненных вызовов они повышают ранг и открывают более тяжёлые эвакуаторы, способные перевозить крупные транспортные средства, такие как грузовики и автобусы, получают доступ к контрактам полиции на конфискацию с более высокой оплатой и личный эвакуатор, который можно настроить. Отслеживайте статистику, такую как общее количество выполненных вызовов, пройденное расстояние при эвакуации и среднее время отклика на вызовы диспетчера. Отображайте эти данные на панели работы, доступной через NPC или приложение телефона. Рассмотрите возможность внедрения системы репутации, где водители, которые быстро реагируют и выполняют работу без повреждений транспортных средств, получают бонусы к оплате, а те, кто игнорирует вызовы или повреждает автомобили, сталкиваются с штрафами. Это создаёт естественный стимул качества, который приносит пользу всему сообществу сервера, обеспечивая надёжность и профессионализм эвакуационных служб.
Обработка крайних случаев и сетевых ситуаций
Системы эвакуации имеют несколько сложных крайних случаев, требующих тщательной обработки. Когда водитель эвакуатора отключается во время буксировки автомобиля, сервер должен обнаружить отключение и безопасно отсоединить и поставить буксируемое транспортное средство на землю, чтобы оно не парило в воздухе и не проваливалось сквозь карту. Если владелец автомобиля входит в игру, пока его машина прикреплена к эвакуатору, он должен видеть свой автомобиль отмеченным как буксируемый в списке транспортных средств с информацией о водителе эвакуатора. Обращайтесь с владением сущностями транспортных средств осторожно, так как натив attachment работает надежно только когда клиент, выполняющий прикрепление, владеет обеими сущностями. Используйте NetworkRequestControlOfEntity убедиться, что клиент водителя эвакуатора контролирует оба транспортных средства перед попыткой их сцепления. Для синхронизации в мультиплеере транслируйте состояние сцепления всем ближайшим игрокам, чтобы они видели буксируемое транспортное средство в правильном положении на своих экранах. State bags хорошо подходят для этого, так как они автоматически реплицируются всем клиентам в радиусе сущности.