Wróć do bloga
Tutorial8 min czytania

Skrypt pracy medyka FiveM: stwórz realistyczny system EMS

Zaprojektuj kompletną pracę medyka na swój serwer FiveM. Ożywianie pacjentów, procedury szpitalne, integracja z ESX i QBCore oraz najlepsze dostępne skrypty EMS.

Agency Scripts

Założyciel i główny deweloper w Agency Scripts

Przegląd systemu Ambulance Job

Skrypt pracy pogotowia (ambulance job) jest niezbędny dla każdego serwera roleplay stawiającego na immersyjną rozgrywkę służb ratunkowych. System EMS obsługuje wszystko: od odbierania zgłoszeń dyspozytorskich, gdy gracz zostanie powalony, po mechaniki leczenia na miejscu zdarzenia, transport do szpitala i wystawianie rachunków medycznych. Dobrze zaprojektowany skrypt pogotowia tworzy angażujący gameplay dla medyków z EMS, jednocześnie dbając o to, by powaleni gracze nie tkwili w nieskończonym oczekiwaniu. Architektura dzieli się na cztery główne podsystemy: system dyspozytorski i powiadomień ostrzegający EMS o zdarzeniach, mechaniki leczenia i stabilizacji dające medykom realne zadania do wykonania, proces przyjęcia do szpitala i rekonwalescencji oraz system fakturowania generujący ekonomiczny koszt obrażeń. Każdy podsystem musi działać niezależnie, aby w przypadku braku graczy EMS na służbie automatyczne systemy awaryjne zajęły się powrotem gracza do zdrowia.

Konfiguracja prac i system służby

Praca w pogotowiu (ambulance job) wymaga jasno określonych stopni z różnymi uprawnieniami na każdym poziomie. Stażyści mogą być w stanie udzielić tylko podstawowej pierwszej pomocy, podczas gdy starsi ratownicy medyczni mogą wdrażać zaawansowane leczenie i mieć dostęp do zastrzeżonych leków. Użyj tabeli konfiguracyjnej przypisującej stopnie do dostępnych akcji, wymaganych przedmiotów i stawek wynagrodzenia. System służby powinien monitorować, którzy gracze EMS są aktualnie aktywni, aby serwer wiedział, kiedy zastosować automatyczny fallback respawnu:

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),
    },
}

Ten minOnDutyForNoAutoRespawn ustawienie kontroluje, kiedy serwer przełącza się między powrotem do zdrowia zależnym od EMS a automatycznym. Gdy na służbie jest mniej niż dwóch ratowników EMS, powaleni gracze otrzymują czasową opcję odrodzenia po odczekaniu skonfigurowanej liczby sekund. Zapobiega to sytuacji, gdy gracze czekają bez końca na pomoc medyczną, która nigdy nie nadejdzie.

System dispatch i powiadomień

Gdy gracz zostanie obezwładniony, system wysyła powiadomienie dyspozytorskie do wszystkich graczy EMS na służbie, pokazując lokalizację, charakter zgłoszenia oraz możliwość przyjęcia wezwania. Zgłoszenie widzi wielu ratowników EMS, ale tylko jeden powinien je przyjąć, aby uniknąć zamieszania. Zaimplementuj system przyjmowania, w którym pierwsza jednostka akceptująca wezwanie ma je przypisane, a pozostali EMS widzą je jako zajęte wraz z identyfikatorem jednostki reagującej:

-- 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)

Dołącz przyczynę śmierci do informacji w dispatchu, aby ratownik EMT mógł przygotować odpowiednie przedmioty medyczne przed przybyciem na miejsce. Typowe przyczyny, takie jak rany postrzałowe, kolizje drogowe, upadki czy utonięcia, wymagają odmiennego podejścia do leczenia, co pogłębia immersję roleplay dla EMS.

Mechaniki leczenia i reanimacji

Mechaniki leczenia powinny być bardziej rozbudowane niż zwykłe naciśnięcie przycisku w celu reanimacji gracza. Stwórz wieloetapowy proces, w którym ratownik medyczny (EMT) musi najpierw ustabilizować pacjenta za pomocą podstawowych zabiegów przed wykonaniem właściwej reanimacji. Każdy etap leczenia zużywa przedmiot z ekwipunku ratownika i zajmuje określony czas z animacją paska postępu. Stan pacjenta można zamodelować za pomocą prostego stanu zdrowia, który poprawia się po każdym zastosowanym zabiegu:

-- 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

Pasek postępu z animacją tworzy realistyczną scenę leczenia, którą mogą obserwować inni gracze, co wzbogaca klimat roleplay. Ustaw defibrylator jako ostatni krok, który faktycznie przywraca do życia, wymagając od ratownika wcześniejszego ustabilizowania pacjenta bandażami i środkami przeciwbólowymi. To wieloetapowe podejście sprawia, że rola EMS jest znacznie ciekawsza niż zwykłe podniesienie jednym kliknięciem.

Rejestracja i rekonwalescencja w szpitalu

Po reanimacji pacjenta w terenie lub przetransportowaniu go do szpitala gracz EMS może zameldować go w łóżku szpitalnym w celu pełnego powrotu do zdrowia. Proces przyjęcia leczy pacjenta całkowicie, nalicza rachunek medyczny i rejestruje wizytę. Łóżka szpitalne powinny być śledzone, aby zapobiec przypisaniu wielu pacjentów do tego samego łóżka. Stwórz system zarządzania łóżkami, który oznacza łóżka jako zajęte i zwalnia je po konfigurowalnym czasie regeneracji:

-- 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

Rachunek medyczny tworzy ekonomiczne konsekwencje śmierci, które równoważą gospodarkę serwera. Rozważ skalowanie rachunku w zależności od tego, jak gracz został ranny, z wyższymi kosztami za lekkomyślne zachowania, takie jak wypadki przy dużej prędkości, w porównaniu z niższymi kosztami za bycie ofiarą przestępstwa. Zachęca to do ostrożnej jazdy i nadaje wagę niebezpiecznym sytuacjom.

Awaryjny system automatycznego respawnu

Gdy żaden gracz EMS nie jest na służbie, serwer musi zapewnić alternatywną ścieżkę powrotu do zdrowia, aby powaleni gracze nie utknęli na stałe. Zaimplementuj odliczanie, które pojawia się po konfigurowalnym opóźnieniu i pozwala graczowi na respawn w szpitalu z automatycznie pobieraną stałą opłatą medyczną. Lokalizacja respawnu powinna znajdować się przy wejściu do szpitala lub w wyznaczonym obszarze rekonwalescencji. Zawsze sprawdzaj aktualną liczbę EMS przed wyświetleniem opcji automatycznego respawnu i natychmiast ją zamykaj, jeśli medyk przyjmie wezwanie podczas odliczania. Ten dwutorowy system gwarantuje, że gracze zawsze mają możliwość odzyskania przytomności, jednocześnie stawiając na pierwszym miejscu interakcję roleplay z EMS, gdy medycy są dostępni.

Materiały medyczne i integracja z ekwipunkiem

Gracze EMS potrzebują dostępu do materiałów medycznych poprzez schowek szpitalny z ograniczeniem frakcyjnym oraz poprzez system wytwarzania lub zakupu. Zaopatrz schowek szpitalny w bandaże, środki przeciwbólowe, szyny, kroplówki, worki z krwią oraz ładunki do defibrylatora. Każda czynność lecznicza zużywa odpowiedni przedmiot, generując stałe zapotrzebowanie na dostawy. Śledź zużycie zapasów w bazie danych, aby monitorować wzorce zużycia i automatycznie uzupełniać schowki szpitalne w konfigurowalnych odstępach czasu. Rozważ umożliwienie wytwarzania części zapasów graczom z rolą farmaceuty lub lekarza, co stworzy dodatkowe możliwości roleplayu i powiązania ekonomiczne między różnymi pracami na serwerze.

Logowanie i monitorowanie wydajności

Rejestruj każdą interakcję EMS do weryfikacji administracyjnej, w tym dane leczonego gracza, zastosowane procedury medyczne, reagującego ratownika medycznego oraz końcowy rachunek medyczny. Zapisuj te logi w dedykowanej tabeli bazy danych i opcjonalnie wysyłaj podsumowania na webhook Discord do monitorowania przez administrację w czasie rzeczywistym. Logi te pomagają rozwiązywać spory, identyfikować graczy EMS niewykonujących swoich obowiązków i śledzić ogólny stan systemu. Pod kątem wydajności zachowaj lekkość logiki sprawdzania śmierci, uruchamiając ją tylko dla graczy, których zdrowie spadło do zera, a nie dla każdego gracza w każdej klatce. Zastosuj architekturę sterowaną zdarzeniami, w której stan śmierci wywołuje pojedyncze zdarzenie serwerowe zamiast odpytywania (polling). System powiadomień dyspozytorni powinien grupować alerty w pakiety zamiast wysyłać pojedyncze zdarzenia do każdego gracza EMS, zmniejszając obciążenie sieci podczas wypadków masowych, gdy wielu graczy pada jednocześnie.

Pasujący skrypt

Gotowe na twój serwer, aktualizacje w zestawie.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.