返回博客
Tutorial3 分钟阅读

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角色扮演的深度。

治疗与复活机制

治疗机制应比简单按键复活更复杂。创建多步骤流程,急救员必须先用基础治疗稳定患者,然后执行实际复活。每个治疗步骤消耗急救员库存中的物品,并需一定时间,伴随进度条动画。患者状况可用简单健康状态建模,随着每次治疗改善:

-- 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玩家,并跟踪整体系统健康状况。在性能方面,通过仅对生命值降至零的玩家运行死亡检测逻辑,而非每帧对所有玩家运行,保持轻量。使用事件驱动架构,由死亡状态触发单个服务器事件,而非轮询。调度通知系统应批量发送警报,而非向每个EMS玩家发送单独事件,以减少多玩家同时倒地时的网络负载。

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。