Bloga Geri Dön
Tutorial11 dk. okuma

FiveM Radyo ve Dispatch Sistemi: PMA, pma-voice ve CAD'ler

FiveM telsiz, dispatch ve CAD sistemlerini kurun. pma-voice kanalları, MDT araçları ve polis, EMS ile mekanik ekipleri için en iyi script'ler.

Agency Scripts

Agency Scripts Kurucusu ve Baş Geliştiricisi

Radyo ve Dispatch Sistemi Mimarisi

Bir telsiz ve dispatch sistemi, bir FiveM roleplay sunucusundaki acil durum hizmetlerinin iletişim belkemiğidir. Polis, EMS ve itfaiye departmanlarının yapılandırılmış telsiz kanalları üzerinden müdahaleleri koordine etmesini, 911 çağrılarından ve otomatik tespit sistemlerinden gelen öncelikli dispatch bildirimlerini almasını ve GPS entegrasyonuyla birim konumlarını gerçek zamanlı takip etmesini sağlar. Mimari, birbirine bağlı üç sistemden oluşur: kimin hangi frekansta konuşabileceğini yöneten telsiz kanalı altyapısı, gelen çağrıları işleyip uygun birimlere yönlendiren dispatch kuyruğu ve birim konumlarını paylaşımlı bir haritada görselleştiren GPS takip katmanı. Basit sohbet tabanlı iletişimin aksine, düzgün bir telsiz sistemi frekans değiştirme, sadece kanala bağlıyken telsiz konuşmalarını duyma ve gerçek dünyadaki acil durum servislerini yansıtan yapılandırılmış iletişim protokollerini izleme atmosferini katar. Tüm sistem hileyi önlemek için sunucu taraflı state yönetimiyle çalışırken, istemci taraflı NUI telsiz arayüzünü ve dispatch panelini sunar.

Radyo Kanalı Sistemi

Radyo kanalları, farklı departman ve birimlerin ses karmaşası olmadan koordine olabilmesi için iletişimi organize eder. Her kanalın bir frekans numarası, mesleklere dayalı erişim kısıtlamaları ve o anda bağlı olan oyuncuların listesi bulunur. Sistem birden fazla kanal türünü destekler: genel iletişim için departman çapında kanallar, kovalama veya banka soygunu müdahalesi gibi belirli operasyonlar için taktiksel kanallar ve polis ile EMS'in ortak olaylarda koordine olabileceği departmanlar arası kanallar. Oyuncular bir radyo eşyası veya NUI arayüzü üzerinden bir kanala katılır ve o kanaldan iletilen tüm ses veya metinler yalnızca aynı frekansa ayarlanmış oyunculara ulaşır. Acil durum uyarıları için dispatch operatörlerinin ve amirlerin tüm kanallara aynı anda yayın yapabileceği bir öncelikli konuşmacı sistemi uygulayın:

Config.RadioChannels = {
    -- Police Department
    { frequency = 1, label = 'PD Main',        jobs = {'police'},          type = 'department' },
    { frequency = 2, label = 'PD Patrol',       jobs = {'police'},          type = 'department' },
    { frequency = 3, label = 'PD Tactical',     jobs = {'police'},          type = 'tactical',  maxUsers = 8 },
    { frequency = 4, label = 'PD Detectives',   jobs = {'police'},          type = 'tactical',  minRank = 3 },

    -- EMS / Fire
    { frequency = 10, label = 'EMS Main',       jobs = {'ambulance'},       type = 'department' },
    { frequency = 11, label = 'EMS Field',      jobs = {'ambulance'},       type = 'department' },
    { frequency = 12, label = 'Fire Main',      jobs = {'fire'},            type = 'department' },

    -- Inter-department
    { frequency = 20, label = 'Emergency Joint', jobs = {'police','ambulance','fire'}, type = 'inter' },
    { frequency = 21, label = 'Command Channel', jobs = {'police','ambulance','fire'}, type = 'command', minRank = 5 },

    -- Civilian (if radio item owned)
    { frequency = 50, label = 'Civilian Band',  jobs = {},                  type = 'open' },
}

-- Server-side channel state
local radioState = {} -- [frequency] = { players = {}, priority = false }

RegisterNetEvent('radio:server:joinChannel', function(frequency)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local channel = nil
    for _, ch in ipairs(Config.RadioChannels) do
        if ch.frequency == frequency then channel = ch break end
    end
    if not channel then return end

    -- Check job access
    if #channel.jobs > 0 then
        local playerJob = Player.PlayerData.job.name
        local hasAccess = false
        for _, job in ipairs(channel.jobs) do
            if job == playerJob then hasAccess = true break end
        end
        if not hasAccess then
            TriggerClientEvent('QBCore:Notify', src, 'No access to this frequency', 'error')
            return
        end
    end

    -- Check rank requirement
    if channel.minRank and Player.PlayerData.job.grade.level < channel.minRank then
        TriggerClientEvent('QBCore:Notify', src, 'Insufficient rank for this channel', 'error')
        return
    end

    -- Check max users for tactical channels
    if channel.maxUsers and radioState[frequency] then
        if #radioState[frequency].players >= channel.maxUsers then
            TriggerClientEvent('QBCore:Notify', src, 'Channel full', 'error')
            return
        end
    end

    -- Leave current channel
    LeaveCurrentChannel(src)

    -- Join new channel
    if not radioState[frequency] then
        radioState[frequency] = { players = {}, priority = false }
    end
    table.insert(radioState[frequency].players, src)

    TriggerClientEvent('radio:client:joined', src, frequency, channel.label)
    TriggerClientEvent('QBCore:Notify', src, 'Tuned to ' .. channel.label .. ' (' .. frequency .. ')', 'success')
end)

10-Kod Sistemi

Ten-code'lar, acil servis iletişimine özgünlük kazandıran ve dispatch sistemi üzerinden hızlı durum güncellemelerine olanak tanıyan standart telsiz kısaltmalarıdır. 10-kodlarını yalnızca basit sohbet makroları olarak uygulamak yerine, memurun dispatch sistemindeki durumunu güncelleyen ve otomatik yanıtları tetikleyen işlevsel komutlar olarak yapılandırın. Bir memur anons geçtiğinde 10-80 (kovalama devam ediyor), sistem dispatch panelindeki durumlarını otomatik olarak güncellemeli, tüm birimler tarafından görülebilen bir kovalama uyarısı oluşturmalı ve memurun aracının GPS takibini başlatmalıdır. Şunu bildirdiklerinde: 10-97 (olay yerine varış), dispatch sistemi onları yanıt verdikleri aktif çağrı için olay yerinde olarak işaretler. Sunucu sahiplerinin kodları departmanlarının tercih ettiği protokole göre özelleştirebilmesi için 10-code sistemini yapılandırılabilir bir tablo olarak oluşturun:

Config.TenCodes = {
    ['10-4']  = { label = 'Acknowledged',           action = nil,                           status = nil },
    ['10-6']  = { label = 'Busy',                    action = nil,                           status = 'busy' },
    ['10-7']  = { label = 'Out of Service',          action = 'setOffDuty',                  status = 'off_duty' },
    ['10-8']  = { label = 'In Service',              action = 'setOnDuty',                   status = 'available' },
    ['10-11'] = { label = 'Traffic Stop',            action = 'createTrafficStop',            status = 'traffic_stop' },
    ['10-15'] = { label = 'Suspect in Custody',      action = nil,                           status = 'transport' },
    ['10-20'] = { label = 'Location Request',        action = 'shareLocation',               status = nil },
    ['10-23'] = { label = 'Arrived at Scene',        action = 'markOnScene',                 status = 'on_scene' },
    ['10-32'] = { label = 'Person with Weapon',      action = 'createAlert',                 status = 'responding', priority = 'high' },
    ['10-41'] = { label = 'Beginning Tour of Duty',  action = 'clockIn',                     status = 'available' },
    ['10-42'] = { label = 'Ending Tour of Duty',     action = 'clockOut',                    status = 'off_duty' },
    ['10-71'] = { label = 'Shooting',                action = 'createAlert',                 status = 'responding', priority = 'critical' },
    ['10-78'] = { label = 'Officer Needs Assistance', action = 'panicButton',                status = 'emergency', priority = 'critical' },
    ['10-80'] = { label = 'Pursuit in Progress',     action = 'startPursuit',                status = 'pursuit', priority = 'high' },
    ['10-97'] = { label = 'Arriving on Scene',       action = 'markOnScene',                 status = 'on_scene' },
    ['10-99'] = { label = 'Officer Down',            action = 'officerDown',                 status = 'emergency', priority = 'critical' },
    ['code4'] = { label = 'No Further Assistance',   action = 'clearScene',                  status = 'available' },
}

RegisterNetEvent('radio:server:tenCode', function(code)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local codeConfig = Config.TenCodes[code]
    if not codeConfig then return end

    -- Update officer status
    if codeConfig.status then
        UpdateUnitStatus(src, codeConfig.status)
    end

    -- Execute associated action
    if codeConfig.action then
        ExecuteCodeAction(codeConfig.action, src, codeConfig)
    end

    -- Broadcast to channel
    local channel = GetPlayerChannel(src)
    if channel then
        local name = GetUnitCallsign(src) or Player.PlayerData.charinfo.firstname
        BroadcastToChannel(channel, {
            type = 'tencode',
            code = code,
            label = codeConfig.label,
            unit = name,
            priority = codeConfig.priority,
        })
    end
end)

Dispatch Çağrı Kuyruğu

Dispatch kuyruğu, gelen tüm acil çağrıları işleyen ve bunları uygun birimlere yönlendiren merkezi merkezdir. Bir sivil 911'i aradığında, bir bankada veya mağazada otomatik bir alarm tetiklendiğinde ya da bir polis memuru destek talep ettiğinde, sistem öncelik düzeyi, konum, açıklama ve gereken hizmet türünü içeren bir dispatch kaydı oluşturur. Kuyruk, dispatch NUI panelinde önceliğe göre sıralanmış tüm aktif çağrıları çağrı zamanı, konum, durum ve hangi birimlerin atandığını gösterecek şekilde listeler. Özel bir dispatch operatörü rolü çağrıları belirli birimlere manuel olarak atayabilir ya da memurlar bir çağrıya tıklayıp kendilerini yanıt veriyor olarak işaretleyerek kendilerini atayabilirler. Yoğun dönemlerde hiçbir acil durumun gözden kaçmamasını sağlamak için, yanıtlanmayan çağrıların yapılandırılabilir bir zaman aşımından sonra önceliğinin arttığı otomatik çağrı yükseltme sistemi uygulayın:

local dispatchQueue = {}
local callIdCounter = 0

function CreateDispatchCall(data)
    callIdCounter = callIdCounter + 1

    local call = {
        id = callIdCounter,
        type = data.type or 'general',          -- police, ems, fire, general
        priority = data.priority or 'standard', -- low, standard, urgent, critical
        code = data.code or nil,                -- e.g., '10-71', '211' (robbery)
        title = data.title,
        description = data.description or '',
        coords = data.coords,
        street = GetStreetName(data.coords),
        blip = data.blip or nil,
        caller = data.caller or 'Unknown',
        callerId = data.callerId or nil,
        timestamp = os.time(),
        status = 'pending',                     -- pending, assigned, responding, on_scene, resolved
        assignedUnits = {},
        escalationTimer = nil,
        metadata = data.metadata or {},
    }

    -- Set escalation timer
    if call.priority ~= 'critical' then
        call.escalationTimer = os.time() + Config.EscalationTimeout
    end

    table.insert(dispatchQueue, call)

    -- Notify appropriate department
    local targetJobs = GetJobsForCallType(call.type)
    local players = QBCore.Functions.GetQBPlayers()

    for _, player in pairs(players) do
        local job = player.PlayerData.job
        if job.onduty and TableContains(targetJobs, job.name) then
            TriggerClientEvent('dispatch:client:newCall', player.PlayerData.source, call)
        end
    end

    -- Play alert sound based on priority
    if call.priority == 'critical' then
        PlayDispatchAlert('critical', targetJobs)
    end

    return call.id
end

-- Automated dispatch triggers
AddEventHandler('banking:robberyStarted', function(bankId, coords)
    CreateDispatchCall({
        type = 'police',
        priority = 'critical',
        code = '211',
        title = 'Bank Robbery in Progress',
        description = 'Silent alarm triggered at ' .. GetBankLabel(bankId),
        coords = coords,
        blip = { sprite = 500, color = 1, scale = 1.5, flash = true },
    })
end)

AddEventHandler('hospital:911call', function(callerId, coords, description, injuries)
    CreateDispatchCall({
        type = 'ems',
        priority = injuries and injuries.maxSeverity >= 80 and 'critical' or 'urgent',
        title = 'Medical Emergency',
        description = description,
        coords = coords,
        callerId = callerId,
        caller = GetPlayerName(callerId),
        blip = { sprite = 153, color = 3, scale = 1.2 },
        metadata = { injuries = injuries },
    })
end)

GPS Takibi ve Birim Haritası

GPS takibi, tüm aktif birimlerin harita üzerindeki konumlarına gerçek zamanlı görünürlük sağlayarak sevk görevlilerinin ve amirlerin bilinçli görevlendirme kararları almasını sağlar. Görevdeki her memurun konumu, genellikle 5 ila 10 saniyede bir olmak üzere yapılandırılabilir bir aralıkla dispatch sistemine iletilir ve sevk operatörleri ile yetkililerin erişebileceği ortak bir NUI harita panelinde gösterilir. Harita, birim blip'lerini departmana ve duruma göre renk kodlu olarak gösterir; simgeler ise müsait, intikalde, olay yerinde veya takipte olduklarını belirtir. Takip esnasında sevk görevlilerinin hareket yönünü görebilmesi için son birkaç dakikadaki birim hareketlerini izleyen bir iz özelliği ekleyin. Aktif dispatch çağrılarının etrafına önerilen müdahale alanını gösteren bir daire çizen çağrı yarıçapı görselleştirmesini uygulayın. Bir sevk görevlisi yeni bir çağrıya en yakın müsait birimi bulmak istediğinde, sistem tüm uygun birimlere olan mesafeyi hesaplayabilir ve en uygun görevlendirmeyi önerebilir. Yoğun sunucularda ağ trafiğini azaltmak için, yalnızca birim minimum bir mesafe eşiğinden fazla hareket ettiğinde konum güncellemeleri göndererek GPS yayınını verimli tutun.

Uyarı Sistemi ve Panik Butonu

Uyarı sistemi, tüm birimlerin acil müdahalesini gerektiren yüksek öncelikli bildirimleri yönetir. En kritik uyarı, bir memur çağrıda bulunduğunda etkinleştirilen panik butonudur 10-78 veya 10-99, yanıp sönen bir blip ve belirgin bir alarm sesi ile GPS konumlarını görevdeki tüm birimlere gönderir. Panik uyarısı normal dispatch sırası önceliğini geçersiz kılmalı ve onaylanana kadar her memurun ekranında belirgin bir şekilde gösterilmelidir. Kademeli olarak artan yanıtları tetikleyen farklı uyarı seviyeleri uygulayın: standart bir uyarı, bildirim sesiyle bir dispatch çağrısı oluşturur; acil bir uyarı, dispatch panelini yanıp söndürür ve bir uyarı sesi çalar; kritik bir uyarı ise siren sesiyle tam ekran bildirim ve uyarı konumuna otomatik GPS rotası tetikler. Otomatik uyarılar diğer sunucu sistemleriyle entegre olur, böylece silah sesi algılama bölgeleri halka açık alanlarda silah ateşlendiğinde uyarı tetikler, hız kameraları hız sınırını aşan araçları işaretler ve mağaza alarm sistemleri soygunlar başladığında dispatch çağrıları oluşturur. Her uyarı türü, aynı alanda tekrarlanan tetiklemelerden kaynaklanan spam'i önlemek için yapılandırılabilir bekleme sürelerine (cooldown) sahiptir.

Dispatch Operatörü Rolü

Dispatch operatörü, bir iş istasyonunda oturan ve acil durum çağrılarının sahadaki birimlere akışını yöneten özel bir roldür. Basitleştirilmiş bir dispatch bildirimi gören memurların aksine, operatör kuyruk yöneticisi, birim listesi, canlı harita ve iletişim araçlarına sahip eksiksiz bir dispatch konsolu NUI'ına sahiptir. Operatör, çağrıları kuyrukta sürükleyerek önceliklendirebilir, yakınlık ve müsaitlik durumuna göre çağrılara belirli birimleri atayabilir, birimler durumu temiz olarak bildirdiğinde çağrıları çözüldü olarak işaretleyebilir ve departman frekansındaki tüm birimlere anons geçebilir. Dispatch operatörlerinin tam konsola erişmek için belirlenmiş bir dispatch masasında oturmalarını zorunlu kılan bir çalışma istasyonu sistemi uygulayın; bu, sahada bulundukları sırada dispatch sistemini kullanmalarını engeller. Operatör rolü, deneyimli operatörlerin departmanlar arası koordinasyon araçlarına ve acil durum anons yeteneklerine erişim kazandığı kendi meslek rütbesi (job grade) ilerlemesine sahip olmalıdır. Operatörlere performansları hakkında geri bildirim sağlamak ve sunucu yöneticilerine personel alımı kararları için veri sunmak amacıyla ortalama yanıt süresi, vardiya başına yanıtlanan çağrılar ve çağrı çözümleme oranı gibi dispatch performans metriklerini takip edin.

Sesli Telsiz Entegrasyonu

pma-voice veya mumble-voip gibi sesli sohbet çözümleri kullanan sunucularda, gerçekçi radyo iletişimi oluşturmak için telsiz sistemini yakınlık sesiyle entegre edin. Bir oyuncu bir tuşa basılı tutarak telsiz kanalında yayın yaptığında, telsiz iletişimini yüz yüze konuşmadan ayırt etmek için uygulanan bir radyo filtresi efektiyle sesi, fiziksel yakınlığa bakılmaksızın aynı frekanstaki tüm oyuncular tarafından duyulmalıdır. Gerçek telsiz yarı çift yönlü (half-duplex) davranışını simüle ederek başka bir oyuncu konuşmadan önce oyuncuların yayın tuşunu bırakması gereken bir susturma (squelch) sistemi uygulayın. Birisi yayına başladığında ve durdurduğunda sesli geri bildirim sağlamak için tuşa basma ve bırakma anlarına telsiz ses efektleri ekleyin. Ses entegrasyonu, telsiz sisteminde tanımlanan kanal erişim kontrollerine saygı göstermelidir; böylece telsiz NUI'si üzerinden bir frekansa katılmamış oyuncular, mumble kanal kimliğini bir şekilde bilseler bile bu frekansta duyamaz veya yayın yapamaz. Sesli sohbeti olmayan sunucularda, bir frekansta gönderilen mesajların gönderenin çağrı kodu ve kanal tanımlayıcısıyla biçimlendirilmiş bir sohbet formatında göründüğü metin tabanlı bir telsiz sistemine geri dönün.

Entegrasyon ve Genişletilebilirlik

Telsiz ve dispatch sisteminiz, diğer kaynakların dahili uygulamayı anlamalarına gerek kalmadan dispatch çağrıları oluşturmak, uyarılar göndermek ve birim durumunu sorgulamak için kullanabilecekleri temiz bir API sağlamalıdır. Şunun gibi fonksiyonları export edin: CreateDispatchCall, SendAlert, GetAvailableUnits, ve GetUnitStatus böylece sunucudaki herhangi bir kaynak dispatch sistemine entegre olabilir. Bir banka soygunu başladığında, bankacılık kaynağı bir çağrı oluşturmak için dispatch export çağrısı yapar. EMS bir 911 çağrısı aldığında, hastane kaynağı bunu aynı dispatch sistemi üzerinden yönlendirir. Bu merkezi yaklaşım, tüm acil durum iletişiminin tutarlı öncelik yönetimi, birim takibi ve kayıt tutma ile tek bir sistem üzerinden akmasını sağlar. Yönetici incelemesi için dispatch çağrı geçmişini veritabanında saklayın ve her görev dönemi için çağrı hacmini, yanıt sürelerini ve birim etkinliğini özetleyen vardiya raporları oluşturun. Polis vuruldu çağrıları veya banka soygunları gibi kritik uyarıların özel bir yetkili kanalında görünmesi için dispatch sistemini Discord webhooklarına bağlayın: böylece sunucu yöneticileri oyun içinde olmadıklarında bile bilgilendirilir.

Başlamaya Hazır mısınız?

Scriptleri mağazamızdan edinin veya destek, güncellemeler ve gelecek yeniliklere göz atmak için Discord'a katılın.