Skip to content

Medical Listeners

Medical listener hooks and lifecycle payloads for 0r-ambulancejob

MedicalListeners lets you attach custom logic to the medical lifecycle without editing the core encrypted files.

Files:

  • modules/medical_listeners/client.lua
  • modules/medical_listeners/server.lua

Keep listeners non-blocking. Do not use Wait or long loops inside these functions because they can run during death-related flows.

Available Functions#

The listener names below are defined on both client and server:

  • MedicalListeners.onPlayerRevive(data)
  • MedicalListeners.onPlayerRecovered(data)
  • MedicalListeners.onPlayerHeal(data)
  • MedicalListeners.onPlayerDeath(data)
  • MedicalListeners.onPlayerLaststand(data)
  • MedicalListeners.onPlayerRespawn(data)
  • MedicalListeners.onPlayerSpawn(data)
  • MedicalListeners.onPlayerDeathStateChange(data)
  • MedicalListeners.onBillingInvoiceCreated(data)
  • MedicalListeners.onBillingInvoicePaid(data)
  • MedicalListeners.onBillingInvoiceCancelled(data)
  • MedicalListeners.onBillingExternalPaymentReceived(data)

Emission Notes#

Currently emitted on both client and server:

  • onPlayerRevive
  • onPlayerRecovered
  • onPlayerDeath
  • onPlayerLaststand
  • onPlayerDeathStateChange

Currently emitted client-side only:

  • onPlayerHeal
  • onPlayerRespawn
  • onPlayerSpawn

Server-side functions for the client-only entries can still exist safely, but the current core does not emit those three listener names on the server.

Currently emitted server-side only:

  • onBillingInvoiceCreated
  • onBillingInvoicePaid
  • onBillingInvoiceCancelled
  • onBillingExternalPaymentReceived

Listener Semantics#

onPlayerRevive#

Triggered when a downed player is revived back to alive. Use this for revive-specific integrations.

onPlayerRecovered#

Triggered when a player returns from dead or laststand to alive. This is broader than revive and can include respawn or sync recovery flows.

onPlayerHeal#

Triggered on the heal branch. Use this for full-heal integrations that are not tied to death recovery.

onPlayerDeath#

Triggered when the player enters dead.

onPlayerLaststand#

Triggered when the player enters laststand.

onPlayerRespawn#

Triggered when the player respawns through the hospital respawn flow.

onPlayerSpawn#

Triggered on framework / game spawn sync points.

onPlayerDeathStateChange#

Triggered on every real death-state transition, such as alive->laststand, laststand->dead, or dead->alive.

onBillingInvoiceCreated#

Triggered after a facility invoice is created and any external invoice id is saved.

onBillingInvoicePaid#

Triggered after a facility invoice is marked paid, facility account deposit is processed, and the facility log is written.

onBillingInvoiceCancelled#

Triggered after an unpaid facility invoice is cancelled.

onBillingExternalPaymentReceived#

Triggered when the external billing bridge reports payment for a matching invoice. This is emitted before the invoice is marked paid by the bridge callback.

Medical Listener Payload#

Each medical lifecycle listener receives a single data table.

Type:

  • MedicalListenerPayload

Type file:

  • modules/types/medical_listeners.lua

Fields:

  • source
  • target
  • previousState
  • newState
  • reason
  • context

Field Descriptions#

source: The actor or source that triggered the action. In command-based revives this can be the admin or EMS player. In local or sync-driven flows this can be the player themselves.

target: The player affected by the medical action.

previousState: The previous medical state. Possible values are alive, laststand, and dead.

newState: The new medical state. Possible values are alive, laststand, and dead.

reason: A short string describing why the listener was triggered.

context: Optional table with extra information about the trigger. The content depends on the flow.

Billing Listener Payload#

Billing listeners receive a billing-specific data table.

Fields:

  • invoice
  • source
  • facility_id
  • invoice_id
  • invoice_no
  • patient_identifier
  • patient_name
  • doctor_identifier
  • doctor_name
  • amount
  • reason
  • status
  • external_provider
  • external_invoice_id
  • context

invoice contains the mapped invoice row, including:

  • id
  • facility_id
  • invoice_no
  • patient_identifier
  • patient_name
  • doctor_identifier
  • doctor_name
  • amount
  • reason
  • preset_key
  • status
  • external_provider
  • external_invoice_id
  • paid_at
  • created_at
  • updated_at

Billing listener context.action can be created, paid, cancelled, or external_paid.

Reason Constants#

Use the shared constants instead of hardcoding strings when possible:

lua
MedicalListeners.REASONS.ADMIN_COMMAND
MedicalListeners.REASONS.EMS_REVIVE
MedicalListeners.REASONS.NPC_DOCTOR
MedicalListeners.REASONS.HOSPITAL_RESPAWN
MedicalListeners.REASONS.FRAMEWORK_SYNC
MedicalListeners.REASONS.FRAMEWORK_SPAWN_SYNC
MedicalListeners.REASONS.STATEBAG_SYNC
MedicalListeners.REASONS.LOAD_SYNC
MedicalListeners.REASONS.DAMAGE
MedicalListeners.REASONS.ADMIN_DAMAGE
MedicalListeners.REASONS.LASTSTAND_TIMEOUT
MedicalListeners.REASONS.LASTSTAND_CRAWL_LIMIT
MedicalListeners.REASONS.DISCONNECT
MedicalListeners.REASONS.CLIENT_SYNC
MedicalListeners.REASONS.COMPAT_EXPORT

Notes:

  • The constants table covers the canonical shared reasons.
  • Some runtime features may pass additional raw reasons such as health_system.

Helper Functions#

These helpers are available in MedicalListeners:

  • MedicalListeners.isAliveState(state)
  • MedicalListeners.isDownedState(state)
  • MedicalListeners.isRecovery(data)
  • MedicalListeners.isRevive(data)
  • MedicalListeners.isRespawn(data)
  • MedicalListeners.matchesReason(data, reason)
  • MedicalListeners.getTransitionKey(data)

Helper Examples#

lua
if MedicalListeners.isRecovery(data) then
    print(("Recovered via %s"):format(data.reason))
end
lua
if MedicalListeners.matchesReason(data, MedicalListeners.REASONS.NPC_DOCTOR) then
    print("Recovery came from NPC doctor")
end

Client Examples#

Play a custom effect on laststand#

lua
function MedicalListeners.onPlayerLaststand(data)
    if data.reason == MedicalListeners.REASONS.DAMAGE then
        ShakeGameplayCam("SMALL_EXPLOSION_SHAKE", 0.15)
    end
end

React only to hospital respawn#

lua
function MedicalListeners.onPlayerRespawn(data)
    if MedicalListeners.isRespawn(data) then
        print(("Respawned at facility %s"):format(data.context and data.context.facilityId or "unknown"))
    end
end

Server Examples#

Log revive source and target#

lua
function MedicalListeners.onPlayerRevive(data)
    print(("Revive source=%s target=%s reason=%s"):format(
        tostring(data.source),
        tostring(data.target),
        tostring(data.reason)
    ))
end

Trigger another resource only for EMS revives#

lua
function MedicalListeners.onPlayerRevive(data)
    if not MedicalListeners.matchesReason(data, MedicalListeners.REASONS.EMS_REVIVE) then
        return
    end

    TriggerEvent("my-resource:server:onEmsRevive", data.target, data.source)
end

Handle all recovery paths from one place#

lua
function MedicalListeners.onPlayerRecovered(data)
    if MedicalListeners.isRespawn(data) then
        TriggerEvent("my-resource:server:onHospitalRecovery", data.target)
        return
    end

    TriggerEvent("my-resource:server:onGeneralRecovery", data.target, data.reason)
end

Listen for paid facility invoices#

lua
function MedicalListeners.onBillingInvoicePaid(data)
    TriggerEvent("my-resource:server:onMedicalInvoicePaid", {
        invoiceId = data.invoice_id,
        facilityId = data.facility_id,
        patient = data.patient_identifier,
        amount = data.amount,
    })
end

Example Payloads#

Revive#

lua
{
    source = 1,
    target = 24,
    previousState = "dead",
    newState = "alive",
    reason = "admin_command",
    context = {
        from = "commands:adminRevive"
    }
}

Death#

lua
{
    source = 24,
    target = 24,
    previousState = "laststand",
    newState = "dead",
    reason = "laststand_timeout",
    context = {
        from = "laststandTimer"
    }
}

Respawn#

lua
{
    source = 24,
    target = 24,
    previousState = "dead",
    newState = "alive",
    reason = "hospital_respawn",
    context = {
        facilityId = 3,
        respawnPoint = {
            x = 0.0,
            y = 0.0,
            z = 0.0,
            w = 0.0
        }
    }
}

Billing Invoice Paid#

lua
{
    invoice_id = 12,
    invoice_no = "EMS-1-260502101530-0042",
    facility_id = 1,
    patient_identifier = "license:xxxx",
    patient_name = "John Doe",
    amount = 750,
    reason = "Revive",
    status = "paid",
    external_provider = "manual",
    external_invoice_id = "",
    context = {
        action = "paid",
        invoice_id = 12
    }
}

Notes#

  • context is not a fixed schema. It changes depending on the action.
  • source does not always mean "the player who revived someone". In sync-based flows it may represent the affected player or system-originated action.
  • Billing listeners are defined in both client and server files for consistency, but they are emitted server-side only.