Назад к блогу
Tutorial9 мин чтения

FiveM Mechanic System: Тюнинг, ремонт и профессии с транспортом

Создайте полноценную систему механика для FiveM. Ремонт, тюнинг, эвакуация на штрафстоянку, интерфейсы магазинов и лучшие скрипты механиков для серверов QBCore и ESX.

Agency Scripts

Основатель и ведущий разработчик Agency Scripts

Проектирование архитектуры работы механика

Система работы механика , одна из самых популярных и полезных для разработки функций на FiveM ролевом сервере. В отличие от простых скриптов ремонта, которые просто вызывают SetVehicleFixed() , полноценная система механика вводит целую экономику работы с специализированными ролями, инвентарями запчастей и взаимодействиями между игроками, которые стимулируют ролевую игру. Архитектура должна учитывать несколько автомастерских по карте, каждая из которых может принадлежать разным группам игроков, с иерархией рангов работы, определяющей, кто может выполнять какие ремонты. В основе нужна общая конфигурация, определяющая расположение мастерских, доступные услуги на каждом уровне, цены на запчасти и ранги работы, необходимые для разблокировки сложных ремонтов, таких как замена двигателя или установка турбо. Система должна интегрироваться с существующей системой работы вашего сервера, будь то QBCore, ESX или кастомное решение, чтобы игроки могли отмечаться на работу и уходить с неё с правильной обработкой зарплаты и учётом статуса дежурства.

Грейды работы и система разрешений

Многоуровневая система рангов работы придаёт профессии механика глубину и стимулирует игроков продвигаться по службе. Начните с определения чётких рангов с конкретными правами: стажёр может только мыть машины и ремонтировать мелкие повреждения кузова, обычный механик , чинить двигатель и менять шины, старший механик , настраивать производительность и красить, а владелец мастерской управляет сотрудниками и устанавливает цены на услуги. Храните определения рангов в общей конфигурации, чтобы клиент и сервер использовали их последовательно. Вот пример конфигурации, сопоставляющей ранги с разрешёнными услугами:

Config.MechanicGrades = {
    [0] = {
        label = 'Trainee',
        services = {'wash', 'body_minor'},
        payMultiplier = 0.7
    },
    [1] = {
        label = 'Mechanic',
        services = {'wash', 'body_minor', 'body_major', 'engine_repair', 'tire_replace', 'brake_repair'},
        payMultiplier = 1.0
    },
    [2] = {
        label = 'Senior Mechanic',
        services = {'wash', 'body_minor', 'body_major', 'engine_repair', 'tire_replace',
                    'brake_repair', 'spray_paint', 'performance_tune', 'turbo_install'},
        payMultiplier = 1.3
    },
    [3] = {
        label = 'Shop Owner',
        services = 'all',
        payMultiplier = 1.5,
        canManage = true
    }
}

Config.ServicePrices = {
    wash = 200,
    body_minor = 800,
    body_major = 2500,
    engine_repair = 3500,
    tire_replace = 1200,
    brake_repair = 1500,
    spray_paint = 5000,
    performance_tune = 15000,
    turbo_install = 25000
}

На стороне сервера всегда проверяйте ранг игрока перед выполнением любой услуги. Никогда не доверяйте клиенту сообщать, какие услуги доступны, так как читер может изменить NUI и отправить любой запрос. Серверный callback должен проверять текущую работу игрока, уровень ранга и наличие запрашиваемой услуги в списке услуг этого ранга перед продолжением ремонта.

Механика ремонта транспортных средств и система запчастей

Реалистичная механика ремонта выходит за рамки одного native вызова. Вместо мгновенного ремонта транспортного средства процесс ремонта разбивается на этапы, которые требуют времени и расходуют запчасти из инвентаря механика. Когда игрок приносит машину на ремонт двигателя, механик должен иметь нужные запчасти в личном инвентаре или в общем складе мастерской. Используйте систему прогресс-бара, чтобы симулировать время ремонта, создавая естественные ролевые моменты, когда клиент ждет и взаимодействует с механиком. Логика ремонта должна нацеливаться на конкретные компоненты транспортного средства с помощью native функций, чтобы можно было ремонтировать двигатель без исправления повреждений кузова или менять шины без влияния на здоровье двигателя:

local RepairFunctions = {
    engine_repair = function(vehicle)
        local parts = {'engine_oil', 'spark_plugs', 'coolant'}
        if not HasRequiredParts(parts) then
            return false, 'Missing required parts'
        end

        RemovePartsFromInventory(parts)

        -- Animate the repair
        TaskTurnPedToFaceEntity(PlayerPedId(), vehicle, 1000)
        Wait(1000)

        if not StartProgressBar('Repairing engine...', 15000, 'mechanic_repair') then
            return false, 'Repair cancelled'
        end

        SetVehicleEngineHealth(vehicle, 1000.0)
        SetVehicleEngineOn(vehicle, true, true, false)
        return true
    end,

    body_major = function(vehicle)
        local parts = {'body_panel', 'filler_putty', 'paint_primer'}
        if not HasRequiredParts(parts) then
            return false, 'Missing required parts'
        end

        RemovePartsFromInventory(parts)

        if not StartProgressBar('Repairing body damage...', 20000, 'mechanic_repair') then
            return false, 'Repair cancelled'
        end

        SetVehicleBodyHealth(vehicle, 1000.0)
        SetVehicleDeformationFixed(vehicle)
        return true
    end,

    tire_replace = function(vehicle, tireIndex)
        local parts = {'tire_set'}
        if not HasRequiredParts(parts) then
            return false, 'Missing required parts'
        end

        RemovePartsFromInventory(parts)

        if not StartProgressBar('Replacing tire...', 8000, 'mechanic_repair') then
            return false, 'Repair cancelled'
        end

        SetVehicleTyreFixed(vehicle, tireIndex)
        return true
    end
}

Система запчастей создаёт дополнительный экономический слой на вашем сервере. Запчасти хранятся у NPC оптовиков или игроки могут изготавливать их на промышленных площадках. Мастерские могут закупать запчасти оптом по оптовым ценам и хранить их в общем инвентаре магазина, что требует от владельца управления цепочкой поставок для бесперебойной работы бизнеса. Эта динамика спроса и предложения добавляет глубину ролевому опыту.

Покраска аэрозолью и косметические услуги

Покраска аэрозолью , одна из самых визуально удовлетворяющих услуг, которые может предложить автомастерская, и для её качественной реализации требуется работа с несколькими нативными функциями цвета транспортного средства. FiveM предоставляет нативы для основного цвета, вторичного цвета, перламутра, цвета колес и пользовательских RGB значений. Создайте UI выбора цвета, который позволит механику выбирать из предустановленных цветов или вводить пользовательские RGB значения за премиальную цену. NUI должен отображать превью в реальном времени, временно применяя цвет к транспортному средству, пока механик просматривает варианты. Вот как обрабатывать серверное применение цвета после подтверждения покраски механиком и оплаты клиентом:

RegisterNetEvent('mechanic:server:applyPaint', function(netId, paintData, customerId)
    local src = source
    local Mechanic = QBCore.Functions.GetPlayer(src)
    local Customer = QBCore.Functions.GetPlayer(customerId)

    if not Mechanic or not Customer then return end
    if Mechanic.PlayerData.job.name ~= 'mechanic' then return end

    local gradeServices = Config.MechanicGrades[Mechanic.PlayerData.job.grade.level].services
    if gradeServices ~= 'all' and not TableContains(gradeServices, 'spray_paint') then
        return TriggerClientEvent('QBCore:Notify', src, 'Not authorized', 'error')
    end

    local price = paintData.isCustomRGB and Config.ServicePrices.spray_paint * 1.5
        or Config.ServicePrices.spray_paint

    if Customer.Functions.RemoveMoney('cash', price, 'mechanic-paint') then
        local shopCut = math.floor(price * 0.6)
        local mechanicCut = price - shopCut
        Mechanic.Functions.AddMoney('cash', mechanicCut, 'mechanic-paint-commission')
        AddToShopFunds(Mechanic.PlayerData.job.grade.level >= 3 and src, shopCut)

        TriggerClientEvent('mechanic:client:applyPaint', customerId, netId, paintData)
        TriggerClientEvent('QBCore:Notify', src, 'Paint applied - earned $' .. mechanicCut, 'success')
        TriggerClientEvent('QBCore:Notify', customerId, 'Vehicle painted for $' .. price, 'success')
    else
        TriggerClientEvent('QBCore:Notify', src, 'Customer cannot afford this', 'error')
    end
end)

Помимо базовой покраски, рассмотрите возможность добавления нанесения ливреи, установки неоновых огней и тонировки окон как отдельных косметических услуг. Каждая услуга должна иметь свои требования к деталям и ценовую политику. Например, неоновые огни требуют неоновые комплекты из инвентаря деталей и используют SetVehicleNeonLightEnabled и SetVehicleNeonLightsColour natives для применения визуального эффекта. Тонировка окон использует SetVehicleWindowTint с разными уровнями тонировки, которые могут быть ограничены локальными законами сервера, давая полиции повод останавливать игроков за незаконные тонировки.

Улучшения производительности и тюнинг

Настройка производительности , это то, где механики могут действительно проявить себя и заработать большие деньги на сервере. FiveM предоставляет обширные нативы для модификации транспортных средств через SetVehicleMod функция, которая принимает тип мода и индекс мода. Типы модов охватывают улучшения двигателя (тип 11), тормозов (тип 12), трансмиссии (тип 13), подвески (тип 15), брони (тип 16) и турбо (тип 18). Каждый тип мода имеет несколько уровней, которые постепенно улучшают характеристики автомобиля. Создайте интерфейс тюнинга, показывающий доступные улучшения для текущего автомобиля, стоимость каждого уровня и желательно сравнительную статистику до и после по скорости, ускорению, торможению и управляемости:

function GetAvailableUpgrades(vehicle)
    local upgrades = {}
    local modTypes = {
        {type = 11, label = 'Engine', icon = 'fa-engine'},
        {type = 12, label = 'Brakes', icon = 'fa-brake'},
        {type = 13, label = 'Transmission', icon = 'fa-gears'},
        {type = 15, label = 'Suspension', icon = 'fa-car'},
        {type = 16, label = 'Armor', icon = 'fa-shield'},
        {type = 18, label = 'Turbo', icon = 'fa-bolt'}
    }

    SetVehicleModKit(vehicle, 0)

    for _, mod in ipairs(modTypes) do
        local currentLevel = GetVehicleMod(vehicle, mod.type)
        local maxLevel = GetNumVehicleMods(vehicle, mod.type)
        local isTurbo = mod.type == 18

        table.insert(upgrades, {
            type = mod.type,
            label = mod.label,
            icon = mod.icon,
            currentLevel = currentLevel,
            maxLevel = isTurbo and 1 or maxLevel,
            installed = isTurbo and IsToggleModOn(vehicle, mod.type) or currentLevel >= 0,
            price = CalculateUpgradePrice(mod.type, currentLevel + 1)
        })
    end

    return upgrades
end

function ApplyPerformanceUpgrade(vehicle, modType, level)
    SetVehicleModKit(vehicle, 0)

    if modType == 18 then
        ToggleVehicleMod(vehicle, 18, true)
    else
        SetVehicleMod(vehicle, modType, level, false)
    end
end

Улучшения производительности должны требовать ценные детали, такие как турбокомплекты, гоночные трансмиссии или спортивные тормозные системы. Эти детали можно получить у специализированных поставщиков или изготовить игрокам с нужными навыками, создавая межпрофильные зависимости, которые обогащают экономику сервера. Всегда проверяйте запросы на улучшения на сервере, чтобы предотвратить применение модификаций, за которые клиент не заплатил или не имеет необходимых деталей.

Система биллинга и распределения доходов

Чистая система выставления счетов связывает весь опыт механика и обрабатывает финансовые операции между механиком, клиентом и мастерской. После завершения услуги механик генерирует детализированный счёт с перечнем выполненных работ и их стоимостью. Счёт показывается клиенту через уведомление или всплывающее окно NUI, которое он должен принять перед оплатой. Доход делится между механиком, выполнившим работу, и бизнес-счётом мастерской, с настраиваемым владельцем магазина соотношением. Вот серверная система выставления счетов, которая обрабатывает создание и оплату счетов:

local activeInvoices = {}

RegisterNetEvent('mechanic:server:createInvoice', function(customerId, services)
    local src = source
    local Mechanic = QBCore.Functions.GetPlayer(src)
    local Customer = QBCore.Functions.GetPlayer(customerId)

    if not Mechanic or not Customer then return end
    if Mechanic.PlayerData.job.name ~= 'mechanic' then return end

    local totalPrice = 0
    local lineItems = {}

    for _, service in ipairs(services) do
        local price = Config.ServicePrices[service.name]
        if price then
            totalPrice = totalPrice + price
            table.insert(lineItems, {
                name = service.label,
                price = price
            })
        end
    end

    local invoiceId = 'INV-' .. os.time() .. '-' .. math.random(1000, 9999)
    activeInvoices[invoiceId] = {
        mechanic = src,
        customer = customerId,
        total = totalPrice,
        items = lineItems,
        shopId = Mechanic.PlayerData.metadata.currentShop,
        timestamp = os.time()
    }

    TriggerClientEvent('mechanic:client:showInvoice', customerId, invoiceId, lineItems, totalPrice)
end)

RegisterNetEvent('mechanic:server:payInvoice', function(invoiceId)
    local src = source
    local invoice = activeInvoices[invoiceId]
    if not invoice or invoice.customer ~= src then return end

    local Customer = QBCore.Functions.GetPlayer(src)
    local Mechanic = QBCore.Functions.GetPlayer(invoice.mechanic)

    if Customer.Functions.RemoveMoney('cash', invoice.total, 'mechanic-invoice') then
        local mechanicShare = math.floor(invoice.total * 0.4)
        local shopShare = invoice.total - mechanicShare

        if Mechanic then
            Mechanic.Functions.AddMoney('cash', mechanicShare, 'mechanic-wage')
            TriggerClientEvent('QBCore:Notify', invoice.mechanic,
                'Received $' .. mechanicShare .. ' for services', 'success')
        end

        UpdateShopBalance(invoice.shopId, shopShare)
        activeInvoices[invoiceId] = nil

        TriggerClientEvent('QBCore:Notify', src, 'Paid $' .. invoice.total, 'success')
    else
        TriggerClientEvent('QBCore:Notify', src, 'Not enough cash', 'error')
    end
end)

Рассмотрите поддержку нескольких способов оплаты, включая наличные, банковские переводы и даже криптовалюту, если на вашем сервере есть криптосистема. Отслеживайте все транзакции в базе данных, чтобы владельцы магазинов могли просматривать историю доходов, выявлять лучших механиков и обнаруживать подозрительные схемы оплаты. Это финансовое отслеживание также позволяет реализовать налоговые системы и взаимодействия с государственными ролевыми элементами, где государство может проводить аудит механиков.

Управление инвентарём тайников и магазинов

Каждому автомастерской нужен общий тайник, где хранятся запчасти и к которому имеют доступ все дежурные сотрудники. Интегрируйте с системой инвентаря вашего сервера, будь то ox_inventory, qb-inventory или кастомное решение, чтобы создать тайник, доступный только авторизованным механикам. Владелец мастерской должен иметь возможность заказывать запчасти у NPC оптового поставщика, что списывает деньги с бизнес-счёта мастерской и добавляет запчасти в тайник после настраиваемой задержки доставки. Эта задержка имитирует реальные логистические процессы и предотвращает мгновенное пополнение. Ведите журнал доступа к тайнику, чтобы владелец видел, какие механики брали какие запчасти, предотвращая внутренние кражи. На клиенте используйте взаимодействие с целью на конкретном объекте в мастерской, например, шкаф для инструментов или полка с запчастями, чтобы открыть тайник. Совместите тайник с системой крафта, где механики могут собирать сложные детали из сырья, например, объединять корпус турбины, крыльчатку и wastegate в полный турбо-комплект, добавляя ещё один навык в работу.

Эвакуаторы и дорожная помощь

Расширение работы механика включением услуг эвакуатора и помощи на дороге значительно увеличивает активность и доходность профессии. Механики должны иметь возможность брать эвакуатор из своего магазина, ехать к месту нахождения застрявшего игрока, прикреплять сломанный транспорт с помощью AttachEntityToEntity native и отбуксируйте его обратно в мастерскую для ремонта. Реализуйте систему диспетчеризации, где игроки могут вызвать помощь на дороге через свой телефон, что создаёт метку на карте, видимую всем дежурным механикам. Первый механик, принявший вызов, получает заказ, и на карту рисуется маршрут GPS до места клиента. Для системы крепления рассчитывайте правильные смещения в зависимости от модели эвакуатора, чтобы буксируемый автомобиль сидел правильно на платформе. Добавьте плату за буксировку, зависящую от расстояния, чтобы более длинные буксировки стоили клиенту дороже, стимулируя механиков обслуживать вызовы в своей зоне, но позволяя брать выгодные дальние заказы по всей карте.

Готовы начать?

Возьмите скрипты в нашем магазине или заходите в Discord за поддержкой, обновлениями и анонсами.