Architecture du système radio et de répartition
Un système de radio et de répartition constitue l'épine dorsale de communication des services d'urgence sur un serveur de jeu de rôle FiveM. Il permet à la police, aux services d'urgence et aux pompiers de coordonner leurs réponses via des canaux radio structurés, de recevoir des alertes de répartition prioritaires provenant des appels au 911 et des systèmes de détection automatisés, et de suivre l'emplacement des unités en temps réel grâce à l'intégration GPS. L'architecture se compose de trois systèmes interconnectés : le cadre de canaux radio qui gère qui peut parler sur quelle fréquence, la file d'attente de répartition qui traite les appels entrants et les achemine vers les unités appropriées, et la couche de suivi GPS qui visualise les positions des unités sur une carte partagée. Contrairement à une simple communication basée sur le chat, un système radio approprié ajoute l'immersion des fréquences de commutation, n'entendant les conversations radio que lorsqu'il est connecté et suivant des protocoles de communication structurés qui reflètent les services d'urgence du monde réel. L'ensemble du système fonctionne via une gestion d'état côté serveur pour empêcher la triche, le NUI côté client fournissant l'interface radio et le panneau de répartition.
Système de canaux radio
Les chaînes radio organisent la communication de manière à ce que les différents départements et unités puissent se coordonner sans interférences. Chaque chaîne possède un numéro de fréquence, des restrictions d'accès basées sur les rôles professionnels et une liste des joueurs actuellement connectés. Le système prend en charge plusieurs types de canaux : des canaux à l'échelle du département pour la communication générale, des canaux tactiques pour des opérations spécifiques telles qu'une poursuite ou un vol de banque, et des canaux interdépartementaux où la police et les services d'urgence peuvent se coordonner sur des incidents partagés. Les joueurs rejoignent une chaîne via un élément radio ou une interface NUI, et toute la voix ou le texte transmis sur cette chaîne atteint uniquement les joueurs réglés sur la même fréquence. Mettez en œuvre un système de haut-parleurs prioritaires où les opérateurs de répartition et les commandants peuvent diffuser simultanément sur tous les canaux pour les alertes d'urgence :
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)
Système à 10 codes
Dix codes sont des raccourcis radio standardisés qui ajoutent de l'authenticité à la communication des services d'urgence et permettent des mises à jour rapides de l'état via le système de répartition. Au lieu d'implémenter 10 codes sous forme de simples macros de discussion, créez-les sous forme de commandes fonctionnelles qui mettent à jour le statut de l'agent dans le système de répartition et déclenchent des réponses automatisées. Lorsqu'un agent appelle 10-80 (poursuite en cours), le système devrait automatiquement mettre à jour leur statut sur le tableau de répartition, créer une alerte de poursuite visible par toutes les unités et commencer le suivi GPS du véhicule de l'officier. Quand ils appellent 10-97 (arrivant sur place), le système de répartition les marque comme étant sur place pour l'appel actif auquel ils répondent. Créez le système à 10 codes sous forme de tableau configurable afin que les propriétaires de serveurs puissent personnaliser les codes en fonction du protocole préféré de leur service :
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)
File d'attente d'appels de répartition
La file d'attente de répartition est la plaque tournante centrale qui traite tous les appels d'urgence entrants et les achemine vers les unités appropriées. Lorsqu'un civil appelle le 911, lorsqu'une alarme automatisée se déclenche dans une banque ou un magasin, ou lorsqu'un policier demande du renfort, le système crée une entrée de répartition avec un niveau de priorité, un emplacement, une description et le type de service requis. La file d'attente affiche tous les appels actifs triés par priorité sur le panneau de répartition NUI, indiquant l'heure de l'appel, l'emplacement, l'état et les unités attribuées. Un rôle d'opérateur de répartition dédié peut attribuer manuellement des appels à des unités spécifiques, ou les agents peuvent s'auto-attribuer en cliquant sur un appel et en se marquant comme répondant. Implémentez une escalade automatique des appels où les appels sans réponse augmentent en priorité après un délai d'attente configurable, garantissant qu'aucune urgence ne soit ignorée pendant les périodes de pointe :
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)
Suivi GPS et carte des unités
Le suivi GPS offre une visibilité en temps réel sur l'emplacement de toutes les unités actives sur la carte, permettant aux répartiteurs et aux commandants de prendre des décisions d'affectation éclairées. La position de chaque agent de service est diffusée au système de répartition à un intervalle configurable, généralement toutes les 5 à 10 secondes, et affichée sur un panneau de carte NUI partagé accessible aux opérateurs de répartition et aux superviseurs. La carte montre les icônes des unités codées par couleur par service et statut, avec des icônes indiquant si elles sont disponibles, en réponse, sur place ou en poursuite. Incluez une fonction de suivi qui suit les mouvements de l'unité au cours des dernières minutes afin que les répartiteurs puissent voir la direction du déplacement pendant les poursuites. Implémentez une visualisation du rayon d'appel qui dessine un cercle autour des appels de répartition actifs indiquant la zone de réponse recommandée. Lorsqu'un répartiteur doit trouver l'unité disponible la plus proche d'un nouvel appel, le système peut calculer les distances de toutes les unités disponibles et suggérer l'affectation optimale. Maintenez l'efficacité de la diffusion GPS en envoyant des mises à jour de position uniquement lorsque l'unité a dépassé un seuil de distance minimum afin de réduire le trafic réseau sur les serveurs occupés.
Système d'alerte et bouton panique
Le système d'alerte gère les notifications hautement prioritaires qui nécessitent une attention immédiate de toutes les unités. L'alerte la plus critique est le bouton de panique, activé lorsqu'un agent appelle 10-78 ou 10-99, qui envoie leur position GPS à toutes les unités en service avec un clignotement et un son d'alarme distinctif. L'alerte de panique doit remplacer la priorité normale de la file d'attente de répartition et s'afficher de manière bien visible sur l'écran de chaque agent jusqu'à ce qu'elle soit reconnue. Implémentez différents niveaux d'alerte qui déclenchent des réponses croissantes : une alerte standard crée un appel de répartition avec un son de notification, une alerte urgente fait clignoter le panneau de répartition et émet une tonalité d'avertissement, et une alerte critique déclenche une notification en plein écran avec un son de sirène et un routage GPS automatique vers l'emplacement de l'alerte. Les alertes automatisées s'intègrent à d'autres systèmes de serveur afin que les zones de détection de coups de feu déclenchent des alertes lorsque des armes sont tirées dans des espaces publics, que les radars signalent les véhicules dépassant les limites de vitesse et que les systèmes d'alarme des magasins créent des appels de répartition lorsque les vols commencent. Chaque type d'alerte dispose de temps de recharge configurables pour empêcher le spam de se déclencher de manière répétée dans la même zone.
Rôle d'opérateur de répartition
L'opérateur de répartition est un rôle dédié qui siège à un poste de travail et gère le flux des appels d'urgence vers les unités de terrain. Contrairement aux agents qui voient une notification de répartition simplifiée, l'opérateur dispose d'une console de répartition complète NUI avec un gestionnaire de files d'attente, une liste d'unités, une carte en direct et des outils de communication. L'opérateur peut hiérarchiser les appels en les faisant glisser dans la file d'attente, attribuer des unités spécifiques aux appels en fonction de la proximité et de la disponibilité, marquer les appels comme résolus lorsque les unités signalent qu'ils sont clairs et diffuser des messages à toutes les unités sur une fréquence de service. Mettez en œuvre un système de poste de travail où les opérateurs de répartition doivent être assis à un bureau de répartition désigné pour accéder à la console complète, les empêchant ainsi d'utiliser le système de répartition sur le terrain. Le rôle d'opérateur doit avoir sa propre progression de niveau de travail où les répartiteurs expérimentés ont accès à des outils de coordination interdépartementaux et à des capacités de diffusion d'urgence. Suivez les mesures de performance de répartition telles que le temps de réponse moyen, les appels traités par équipe et le taux de résolution des appels pour fournir aux opérateurs des commentaires sur leurs performances et fournir aux administrateurs de serveur des données pour les décisions de dotation en personnel.
Intégration de la radio vocale
Pour les serveurs utilisant des solutions de chat vocal telles que pma-voice ou mumble-voip, intégrez le système radio avec la voix de proximité pour créer une communication radio réaliste. Lorsqu'un joueur transmet sur une chaîne radio en maintenant une touche enfoncée, sa voix doit être entendue par tous les joueurs sur la même fréquence, quelle que soit la proximité physique, avec un effet de filtre radio appliqué pour distinguer la communication radio de la conversation en face à face. Implémentez un système de silencieux dans lequel les joueurs doivent relâcher leur clé de transmission avant qu'un autre joueur puisse parler, simulant ainsi le comportement radio semi-duplex réel. Ajoutez des effets sonores radio pour les touches enfoncées et enfoncées afin de fournir un retour audio lorsque quelqu'un commence et arrête de transmettre. L'intégration vocale doit respecter les contrôles d'accès aux canaux définis dans le système radio, afin que les joueurs qui n'ont pas rejoint une fréquence via la radio NUI ne puissent pas entendre ou transmettre sur celle-ci même s'ils connaissent d'une manière ou d'une autre l'ID du canal Mumble. Pour les serveurs sans chat vocal, optez pour un système radio textuel où les messages envoyés sur une fréquence apparaissent dans un format de chat stylisé avec l'indicatif de l'expéditeur et l'identifiant du canal.
Intégration et extensibilité
Ton système radio et de répartition doit fournir un API propre que d'autres ressources peuvent utiliser pour créer des appels de répartition, envoyer des alertes et interroger l'état de l'unité sans avoir besoin de comprendre l'implémentation interne. Fonctions d'exportation comme CreateDispatchCall, SendAlert, GetAvailableUnits, et GetUnitStatus afin que toute ressource sur le serveur puisse s'intégrer au système de répartition. Lorsqu'un braquage de banque commence, la ressource bancaire appelle l'exportation de répartition pour créer un appel. Lorsque EMS reçoit un appel 911, la ressource hospitalière l’achemine via le même système de répartition. Cette approche centralisée garantit que toutes les communications d'urgence transitent par un système unique avec une gestion cohérente des priorités, un suivi des unités et une journalisation. Stockez l'historique des appels de répartition dans la base de données pour examen par l'administrateur et générez des rapports de quart de travail qui résument le volume d'appels, les temps de réponse et l'activité de l'unité pour chaque période de service. Connectez le système de répartition aux webhooks Discord afin que les alertes critiques telles que les appels d'officiers ou les vols de banque apparaissent dans un canal dédié au personnel, tenant ainsi les administrateurs de serveur informés même lorsqu'ils ne sont pas en jeu.
