Visión general del sistema de trabajo de ambulancia
Un script de trabajo de ambulancia es esencial para cualquier servidor de rol que aspire a tener un juego de emergencias inmersivo. El sistema EMS gestiona todo, desde recibir las llamadas de despacho cuando un jugador cae hasta los mecanismos de atención en el lugar, el transporte al hospital y la facturación médica. Un script de ambulancia bien diseñado crea un juego con sentido para los jugadores EMS y a la vez asegura que los caídos no se queden esperando eternamente. La arquitectura se divide en cuatro grandes subsistemas: el sistema de despacho y notificación que alerta a los EMS de los incidentes, los mecanismos de tratamiento y estabilización que dan tareas reales a los médicos, el proceso de admisión en el hospital y recuperación y el sistema de facturación que crea un coste económico por heridas. Cada subsistema debe funcionar de forma independiente para que, si no hay EMS de servicio, sistemas de fallback automáticos se encarguen de la recuperación del jugador.
Configuración del trabajo y sistema de servicio
El trabajo de ambulancia necesita grados bien definidos con capacidades distintas en cada nivel. Un aprendiz podría realizar solo primeros auxilios básicos, mientras que un paramédico sénior puede administrar tratamientos avanzados y acceder a suministros médicos restringidos. Usa una tabla de configuración que mapee grados del trabajo con acciones disponibles, ítems requeridos y tasas de pago. El sistema de servicio debería llevar la cuenta de qué jugadores EMS están activos para que el servidor sepa si debe activar el fallback automático de respawn:
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, -- segundos antes del auto-respawn cuando no hay 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),
},
}
La opción minOnDutyForNoAutoRespawn controla cuándo el servidor cambia entre recuperación dependiente de EMS y recuperación automática. Cuando hay menos de dos EMS de servicio, los caídos reciben una opción de respawn por temporizador tras esperar los segundos configurados. Así se evita que los jugadores se queden atascados esperando una atención médica que nunca llegará.
Sistema de despacho y notificaciones
Cuando un jugador queda incapacitado, el sistema difunde una alerta de despacho a todos los EMS de servicio mostrando la ubicación, la naturaleza de la emergencia y una forma de asumir la llamada. Varios EMS pueden ver la alerta, pero solo uno debe asumirla para evitar confusión. Implementa un sistema de reclamación en el que el primero que acepte la llamada la reciba asignada y el resto la vea como asumida con el identificador de la unidad que responde:
-- Servidor: gestiona el evento de jugador caído
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,
}
-- Notifica a todos los EMS de servicio
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
-- Notifica a todos los EMS de que la llamada ha sido asumida
local emsList = QBCore.Functions.GetPlayersOnDuty('ambulance')
for _, emsId in ipairs(emsList) do
TriggerClientEvent('ambulance:client:callClaimed', emsId, callId, Player.PlayerData.charinfo.firstname)
end
-- Marca waypoint GPS para el EMT que responde
TriggerClientEvent('ambulance:client:setWaypoint', src, call.coords)
end)
Incluye la causa del fallecimiento en la información del despacho para que el EMT que responde pueda preparar los ítems de tratamiento adecuados antes de llegar. Causas habituales como heridas de bala, colisiones de vehículos, caídas y ahogamientos requieren enfoques de tratamiento distintos, lo que aporta profundidad al rol del EMS.
Mecánicas de tratamiento y reanimación
Las mecánicas de tratamiento deberían ser más elaboradas que pulsar un botón para revivir a alguien. Crea un proceso en varios pasos donde el EMT primero estabilice al paciente con tratamientos básicos antes de realizar la reanimación. Cada paso consume un ítem del inventario del EMT y tarda un tiempo fijo con una animación de barra de progreso. El estado del paciente puede modelarse con un estado de salud sencillo que mejora con cada tratamiento aplicado:
-- Cliente: sistema de tratamiento
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
-- Comprueba si el EMT tiene el ítem necesario
if not HasItem(treatment.item) then
QBCore.Functions.Notify('Missing: ' .. treatment.label, 'error')
return
end
-- Reproduce animación y barra de progreso
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() -- éxito
TriggerServerEvent('ambulance:server:applyTreatment',
targetId, treatmentType)
ClearPedTasks(PlayerPedId())
end,
function() -- cancelación
ClearPedTasks(PlayerPedId())
QBCore.Functions.Notify('Treatment cancelled', 'error')
end
)
end
La barra de progreso con animación crea una escena de tratamiento realista que otros jugadores pueden observar, potenciando el ambiente de rol. Haz que el desfibrilador sea el paso final que realiza la reanimación, obligando al EMT a estabilizar antes al paciente con vendas y analgésicos. Este enfoque en varios pasos hace el rol de EMS mucho más atractivo que un simple revive de un clic.
Admisión en el hospital y recuperación
Después de reanimar a un paciente en el lugar o transportarlo al hospital, un jugador EMS puede ingresarlo en una cama para su recuperación total. El proceso de ingreso cura al paciente por completo, cobra una factura médica y registra la visita. Las camas del hospital deberían controlarse para evitar asignar varios pacientes a la misma. Crea un sistema de gestión de camas que marque las ocupadas y las libere tras un tiempo de recuperación configurable:
-- Servidor: gestión de camas del hospital
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
-- Busca una cama disponible
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
-- Calcula y cobra la factura médica
local bill = CalculateMedicalBill(Patient)
Patient.Functions.RemoveMoney('bank', bill, 'medical-bill')
-- Cura al paciente y teletransporta a la cama
local bed = Config.AmbulanceJob.bedSpawns[bedIndex]
TriggerClientEvent('ambulance:client:bedRecovery', patientId, bed, bill)
-- Paga al EMT por el servicio
local salary = Config.AmbulanceJob.grades[EMT.PlayerData.job.grade.level].salary
EMT.Functions.AddMoney('bank', salary, 'ems-treatment-pay')
-- Libera la cama tras la recuperación
SetTimeout(60000, function()
OccupiedBeds[bedIndex] = nil
end)
end)
function CalculateMedicalBill(Patient)
local baseCost = 500
local injuryMultiplier = 1.0
-- Podría tener en cuenta gravedad, cantidad de tratamientos, etc.
return math.floor(baseCost * injuryMultiplier)
end
La factura médica crea una consecuencia económica por morir que equilibra la economía del servidor. Plantéate escalar el importe según cómo se hirió el jugador: más caro para conductas temerarias como choques a alta velocidad y más barato para víctimas de un delito. Esto anima a conducir con cuidado y da peso a las situaciones peligrosas.
Sistema de auto-respawn como fallback
Cuando no hay jugadores EMS de servicio, el servidor debe ofrecer un camino alternativo de recuperación para que los caídos no queden atrapados permanentemente. Implementa un temporizador regresivo que aparezca tras un retraso configurable, permitiendo al jugador reaparecer en el hospital con una tarifa médica plana descontada automáticamente. El punto de respawn debería estar en la entrada del hospital o en una zona de recuperación designada. Comprueba siempre el número actual de EMS antes de mostrar la opción de auto-respawn y descártala de inmediato si un EMT asume la llamada durante la cuenta atrás. Este sistema de doble vía garantiza que los jugadores siempre tengan una forma de recuperarse mientras se prioriza la experiencia de rol con los EMS cuando hay médicos disponibles.
Suministros médicos e integración con el inventario
Los jugadores EMS necesitan acceder a suministros médicos a través de un stash restringido por trabajo en el hospital y mediante un sistema de crafting o compra. Llena el stash del hospital con vendas, analgésicos, férulas, sueros intravenosos, bolsas de sangre y cargas de desfibrilador. Cada acción de tratamiento consume el ítem correspondiente, creando una demanda continua de reabastecimiento. Registra el uso de suministros en la base de datos para monitorizar patrones de consumo y reabastecer automáticamente los stashes del hospital a intervalos configurables. Plantéate que algunos suministros sean crafteables por jugadores con el rol de farmacéutico o doctor, lo que crea oportunidades adicionales de rol y conexiones económicas entre distintos trabajos del servidor.
Logs y monitorización de rendimiento
Registra cada interacción de EMS para revisión administrativa, incluidos a quién se trató, qué tratamientos se aplicaron, el EMT que respondió y la factura médica final. Guarda esos logs en una tabla dedicada de la base de datos y, opcionalmente, envía resúmenes a un webhook de Discord para monitorización en tiempo real por parte del staff. Estos registros ayudan a resolver disputas, identificar a jugadores EMS que no cumplen sus deberes y monitorizar la salud general del sistema. Respecto al rendimiento, mantén la lógica de detección de muerte ligera ejecutándola solo sobre jugadores cuya salud ha caído a cero, no sobre todos los jugadores en cada frame. Usa arquitectura orientada a eventos en la que el estado de muerte dispare un único evento de servidor en lugar de hacer polling. El sistema de notificaciones de despacho debería enviar alertas en lotes en lugar de eventos individuales a cada EMS, reduciendo la sobrecarga de red en escenarios de bajas masivas en los que caen varios jugadores a la vez.
