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

Скрипт работы скорой помощи FiveM: создание реалистичной EMS системы

Разработайте полноценную работу скорой помощи для вашего сервера FiveM. Воскрешение пациентов, рабочие процессы в больнице, интеграция с ESX и QBCore, а также лучшие EMS скрипты.

Agency Scripts

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

Обзор системы работы скорой помощи

Скрипт работы скорой помощи необходим для любого ролевого сервера, стремящегося к реалистичному геймплею экстренных служб. Система EMS обрабатывает всё: от получения вызовов при падении игрока, до механик лечения на месте, транспортировки в больницу и медицинского выставления счетов. Хорошо продуманный скрипт скорой создаёт значимый геймплей для EMS игроков и при этом не оставляет упавших игроков ждать бесконечно. Архитектура делится на четыре основных подсистемы: система диспетчеризации и уведомлений, оповещающая EMS о происшествиях; механики лечения и стабилизации, дающие медикам реальные задачи; процесс регистрации и восстановления в больнице; и система выставления счетов, создающая экономическую стоимость травм. Каждая подсистема должна работать независимо, чтобы при отсутствии EMS игроков на дежурстве автоматические системы обеспечивали восстановление игроков.

Конфигурация работы и система дежурств

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

Config.AmbulanceJob = {
    name = 'ambulance',
    grades = {
        [0] = {
            label = 'Trainee',
            treatments = {'bandage', 'painkillers'},
            canRevive = false,
            salary = 250,
        },
        [1] = {
            label = 'EMT',
            treatments = {'bandage', 'painkillers', 'splint', 'iv_drip'},
            canRevive = true,
            salary = 400,
        },
        [2] = {
            label = 'Paramedic',
            treatments = {'bandage', 'painkillers', 'splint', 'iv_drip',
                         'defibrillator', 'blood_transfusion'},
            canRevive = true,
            salary = 550,
        },
        [3] = {
            label = 'Chief',
            treatments = {'all'},
            canRevive = true,
            salary = 750,
        },
    },
    minOnDutyForNoAutoRespawn = 2,
    respawnTimer = 300,  -- seconds before auto-respawn when no EMS
    hospitalCoords = vector3(311.2, -584.3, 43.3),
    bedSpawns = {
        vector4(309.7, -583.8, 43.3, 70.0),
        vector4(313.0, -585.2, 43.3, 70.0),
        vector4(316.3, -586.6, 43.3, 70.0),
    },
}

Параметр minOnDutyForNoAutoRespawn настройка контролирует, когда сервер переключается между зависимым от EMS и автоматическим восстановлением. Когда на дежурстве менее двух EMS, у упавших игроков появляется возможность возрождения по таймеру после ожидания заданного количества секунд. Это предотвращает ситуацию, когда игроки застревают в ожидании медицинской помощи, которая никогда не придет.

Система диспетчеризации и уведомлений

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

-- Server: handle player down event
local ActiveCalls = {}

RegisterNetEvent('ambulance:server:playerDown', function(deathCause)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local ped = GetPlayerPed(src)
    local coords = GetEntityCoords(ped)
    local streetHash, _ = GetStreetNameAtCoord(coords.x, coords.y, coords.z)
    local streetName = GetStreetNameFromHashKey(streetHash)

    local callId = 'EMS-' .. os.time() .. '-' .. src
    ActiveCalls[callId] = {
        source = src,
        patient = Player.PlayerData.charinfo.firstname .. ' ' ..
                  Player.PlayerData.charinfo.lastname,
        coords = coords,
        street = streetName,
        cause = deathCause,
        time = os.time(),
        claimed = false,
        responder = nil,
    }

    -- Notify all on-duty EMS
    local emsList = QBCore.Functions.GetPlayersOnDuty('ambulance')
    for _, emsId in ipairs(emsList) do
        TriggerClientEvent('ambulance:client:newCall', emsId, callId, ActiveCalls[callId])
    end
end)

RegisterNetEvent('ambulance:server:claimCall', function(callId)
    local src = source
    local call = ActiveCalls[callId]
    if not call or call.claimed then return end

    local Player = QBCore.Functions.GetPlayer(src)
    if not Player or Player.PlayerData.job.name ~= 'ambulance' then return end

    call.claimed = true
    call.responder = src

    -- Notify all EMS that call is claimed
    local emsList = QBCore.Functions.GetPlayersOnDuty('ambulance')
    for _, emsId in ipairs(emsList) do
        TriggerClientEvent('ambulance:client:callClaimed', emsId, callId, Player.PlayerData.charinfo.firstname)
    end

    -- Set GPS waypoint for responding EMT
    TriggerClientEvent('ambulance:client:setWaypoint', src, call.coords)
end)

Включите причину смерти в информацию для диспетчера, чтобы реагирующий EMT мог подготовить соответствующие предметы для лечения до прибытия на место. Распространённые причины, такие как огнестрельные ранения, столкновения транспортных средств, падения и утопления, требуют разных подходов к лечению, что добавляет глубину ролевому опыту EMS.

Механики лечения и оживления

Механика лечения должна быть более сложной, чем просто нажатие кнопки для оживления. Создайте многоэтапный процесс, в котором EMT сначала стабилизирует пациента базовыми процедурами, прежде чем выполнять само оживление. Каждый этап лечения потребляет предмет из инвентаря EMT и занимает определенное время с анимацией прогресс-бара. Состояние пациента можно моделировать простой шкалой здоровья, которая улучшается с каждым примененным лечением:

-- Client: treatment system
local TreatmentSteps = {
    ['bandage'] = {
        label = 'Apply Bandage',
        duration = 5000,
        anim = {dict = 'mini@cpr@char_a@cpr_str', name = 'cpr_pumpchest'},
        healthRestore = 10,
        item = 'bandage',
    },
    ['painkillers'] = {
        label = 'Administer Painkillers',
        duration = 3000,
        anim = {dict = 'mp_arresting', name = 'a_uncuff'},
        healthRestore = 15,
        item = 'painkillers',
    },
    ['defibrillator'] = {
        label = 'Use Defibrillator',
        duration = 8000,
        anim = {dict = 'mini@cpr@char_a@cpr_str', name = 'cpr_pumpchest'},
        healthRestore = 0,
        item = 'defibrillator',
        canRevive = true,
    },
}

function PerformTreatment(targetId, treatmentType)
    local treatment = TreatmentSteps[treatmentType]
    if not treatment then return end

    -- Check if EMT has required item
    if not HasItem(treatment.item) then
        QBCore.Functions.Notify('Missing: ' .. treatment.label, 'error')
        return
    end

    -- Play animation and progress bar
    TaskPlayAnim(PlayerPedId(), treatment.anim.dict, treatment.anim.name,
        8.0, -8.0, treatment.duration, 1, 0, false, false, false)

    QBCore.Functions.Progressbar('treatment_' .. treatmentType,
        treatment.label, treatment.duration, false, true, {}, {}, {}, {},
        function() -- success
            TriggerServerEvent('ambulance:server:applyTreatment',
                targetId, treatmentType)
            ClearPedTasks(PlayerPedId())
        end,
        function() -- cancel
            ClearPedTasks(PlayerPedId())
            QBCore.Functions.Notify('Treatment cancelled', 'error')
        end
    )
end

Полоса прогресса с анимацией создаёт реалистичную сцену лечения, которую могут наблюдать другие игроки, усиливая атмосферу ролевой игры. Сделайте дефибриллятор финальным шагом, который действительно выполняет оживление, требуя от EMT сначала стабилизировать пациента бинтами и обезболивающими. Такой многоступенчатый подход делает роль EMS более увлекательной, чем простое оживление одним кликом.

Регистрация и восстановление в больнице

После оживления пациента на месте или транспортировки в больницу игрок EMS может оформить его в больничную койку для полного восстановления. Процесс оформления полностью лечит пациента, выставляет медицинский счет и фиксирует визит. Койки в больнице должны отслеживаться, чтобы избежать назначения нескольких пациентов на одну и ту же койку. Создайте систему управления койками, которая отмечает койки как занятые и освобождает их после настраиваемого времени восстановления:

-- Server: hospital bed management
local OccupiedBeds = {}

RegisterNetEvent('ambulance:server:checkInPatient', function(patientId)
    local src = source
    local EMT = QBCore.Functions.GetPlayer(src)
    local Patient = QBCore.Functions.GetPlayer(patientId)

    if not EMT or not Patient then return end
    if EMT.PlayerData.job.name ~= 'ambulance' then return end

    -- Find available bed
    local bedIndex = nil
    for i, bed in ipairs(Config.AmbulanceJob.bedSpawns) do
        if not OccupiedBeds[i] then
            bedIndex = i
            break
        end
    end

    if not bedIndex then
        TriggerClientEvent('QBCore:Notify', src, 'No beds available', 'error')
        return
    end

    OccupiedBeds[bedIndex] = patientId

    -- Calculate and charge medical bill
    local bill = CalculateMedicalBill(Patient)
    Patient.Functions.RemoveMoney('bank', bill, 'medical-bill')

    -- Heal patient and teleport to bed
    local bed = Config.AmbulanceJob.bedSpawns[bedIndex]
    TriggerClientEvent('ambulance:client:bedRecovery', patientId, bed, bill)

    -- Pay EMT for service
    local salary = Config.AmbulanceJob.grades[EMT.PlayerData.job.grade.level].salary
    EMT.Functions.AddMoney('bank', salary, 'ems-treatment-pay')

    -- Release bed after recovery
    SetTimeout(60000, function()
        OccupiedBeds[bedIndex] = nil
    end)
end)

function CalculateMedicalBill(Patient)
    local baseCost = 500
    local injuryMultiplier = 1.0
    -- Could factor in injury severity, treatment count, etc.
    return math.floor(baseCost * injuryMultiplier)
end

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

Система автоматического возрождения при сбое

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

Интеграция медицинских запасов и инвентаря

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

Логирование и мониторинг производительности

Логировать каждое взаимодействие EMS для административного обзора, включая кого лечили, какие процедуры применялись, ответственного EMT и итоговый медицинский счет. Хранить эти логи в отдельной таблице базы данных и при необходимости отправлять сводки в Discord webhook для мониторинга персонала в реальном времени. Эти логи помогают разрешать споры, выявлять EMS игроков, не выполняющих свои обязанности, и отслеживать общее состояние системы. С точки зрения производительности, держите логику проверки смерти легковесной, выполняя её только для игроков с нулевым здоровьем, а не для каждого игрока на каждом кадре. Используйте событийно-ориентированную архитектуру, где состояние смерти вызывает одно серверное событие вместо опроса. Система уведомлений dispatch должна группировать оповещения, а не отправлять отдельные события каждому EMS игроку, снижая нагрузку на сеть при массовых случаях, когда несколько игроков одновременно падают.

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

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