Browse docs

Patient & Vital Data

Read the numeric Sky Ambulancejob patient profile safely, understand units and activity values, and build server-authoritative integrations.

Numeric API Version

This page documents the upcoming numeric-vitals major release. Publish it together with the matching Sky Ambulancejob resource version. Earlier releases expose a different string-based contract and are not compatible with the examples below.

The numeric release has one vital-sign model. It does not expose legacy state strings and does not add a second vitalSignsV2 object beside the patient profile.

Every valid profile uses:

profile.vitalSchemaVersion == 2

If an integration receives another schema version, it must stop reading the profile and report an unsupported version. Do not guess values or fall back to string states.

Client Export

Use GetLocalInjuryProfile() to read the local player's active injury profile. It returns nil when no profile is active.

client.lua
local profile = exports["sky_ambulancejob"]:GetLocalInjuryProfile()

if not profile then
    print("No active injury profile")
    return
end

The export returns an internal snapshot for reading. Treat every returned table as read-only. Changing a field does not constitute a supported treatment and can desynchronise your local copy.

Profile Contract

local profile = {
    vitalSchemaVersion = 2,

    -- Resting or initial reference values.
    baselineVitals = {
        heart_rate = 78,
        blood_pressure = {
            systolic = 118,
            diastolic = 76
        },
        oxygen_level = 98,
        blood_sugar = 100
    },

    -- Server-calculated medical targets.
    vitalTargets = {
        heart_rate = 78,
        blood_pressure = {
            systolic = 118,
            diastolic = 76
        },
        oxygen_level = 98,
        blood_sugar = 100
    },

    -- Current values shown by monitors and diagnosis interfaces.
    vitals = {
        heart_rate = 117,
        blood_pressure = {
            systolic = 135,
            diastolic = 76
        },
        oxygen_level = 98,
        blood_sugar = 100
    },

    conditions = {
        cardiacArrest = false,
        activityMode = "run",
        activityLoad = 0.55
    }
}

Field Semantics

FieldMeaning
vitalSchemaVersionRequired schema discriminator. Numeric profiles use 2.
baselineVitalsResting or initial reference values.
vitalTargetsServer-calculated targets from injuries, conditions, and treatments.
vitalsCurrent values, including gradual changes and temporary activity effects.
conditions.cardiacArrestExplicit cardiac-arrest state.
conditions.activityModeLast activity mode accepted by the server.
conditions.activityLoadSmoothed activity load from 0.0 to 1.0.

Accepted activity modes are rest, walk, run, sprint, swim, dive, climb, and combat. Treat any other value as an invalid profile.

Use vitals for HUDs, bedside monitors, and live displays. vitalTargets describes where the medical model is currently moving, not what a monitor should display at that instant.

Do not infer cardiac arrest from low blood pressure or unconsciousness. Read conditions.cardiacArrest.

Units

KeyTypeUnitAccepted range
heart_ratenumberbeats per minute (bpm)0 to 220
blood_pressure.systolicnumbermillimetres of mercury (mmHg)0 to 260
blood_pressure.diastolicnumbermillimetres of mercury (mmHg)0 to 160
oxygen_levelnumberoxygen saturation (% SpO2)0 to 100
blood_sugarnumbermilligrams per decilitre (mg/dL)20 to 600

All values must be finite numbers. Blood pressure must be either 0/0 for cardiac arrest or have a systolic value greater than its diastolic value.

Additional profile fields may be added in future releases. Ignore unknown fields and require a new parser only when vitalSchemaVersion changes.

Validated Reader

The following helper rejects missing, mixed, or unsupported profiles instead of inventing values:

client.lua
local accepted_activity_modes = {
    rest = true,
    walk = true,
    run = true,
    sprint = true,
    swim = true,
    dive = true,
    climb = true,
    combat = true
}

local function is_number_in_range(value, minimum, maximum)
    return type(value) == "number"
        and value == value
        and value > -math.huge
        and value < math.huge
        and value >= minimum
        and value <= maximum
end

local function is_vital_set(value)
    if type(value) ~= "table" then return false end

    local pressure = value.blood_pressure
    if type(pressure) ~= "table"
        or not is_number_in_range(pressure.systolic, 0, 260)
        or not is_number_in_range(pressure.diastolic, 0, 160)
    then
        return false
    end

    local valid_pressure = pressure.systolic == 0 and pressure.diastolic == 0
        or pressure.systolic > pressure.diastolic

    return valid_pressure
        and is_number_in_range(value.heart_rate, 0, 220)
        and is_number_in_range(value.oxygen_level, 0, 100)
        and is_number_in_range(value.blood_sugar, 20, 600)
end

local function is_valid_activity_state(conditions)
    return type(conditions) == "table"
        and type(conditions.cardiacArrest) == "boolean"
        and accepted_activity_modes[conditions.activityMode] == true
        and is_number_in_range(conditions.activityLoad, 0, 1)
end

local function get_local_vital_profile()
    local profile = exports["sky_ambulancejob"]:GetLocalInjuryProfile()
    if type(profile) ~= "table" then
        return nil, "no_profile"
    end

    if profile.vitalSchemaVersion ~= 2 then
        return nil, "unsupported_schema"
    end

    if not is_vital_set(profile.vitals) then
        return nil, "invalid_vitals"
    end

    if not is_vital_set(profile.baselineVitals)
        or not is_vital_set(profile.vitalTargets)
    then
        return nil, "invalid_references"
    end

    if not is_valid_activity_state(profile.conditions) then
        return nil, "invalid_conditions"
    end

    return profile
end

Keep the failure reason in logs during development. Production HUDs should hide or mark unavailable data rather than display fabricated normal values.

Display Example

client.lua
local profile, reason = get_local_vital_profile()
if not profile then
    print("Vital profile unavailable: " .. tostring(reason))
    return
end

local vitals = profile.vitals
local pressure = vitals.blood_pressure

print(("Pulse: %.0f bpm"):format(vitals.heart_rate))
print(("Blood pressure: %.0f / %.0f mmHg"):format(
    pressure.systolic,
    pressure.diastolic
))
print(("Oxygen: %.0f%% SpO2"):format(vitals.oxygen_level))
print(("Blood glucose: %.0f mg/dL"):format(vitals.blood_sugar))

Activity-Aware Displays

Running, sprinting, swimming, diving, or climbing can temporarily move vitals away from vitalTargets. The server controls this behaviour.

For a live monitor:

  • display vitals;
  • optionally show conditions.activityMode;
  • expect values to recover gradually after activity stops;
  • do not overwrite the values with locally calculated Native results;
  • do not treat activity alone as a medical emergency.

The client may detect activity, but the server validates the mode and calculates the actual vital changes. External resources must never submit arbitrary heart-rate, blood-pressure, oxygen, or blood-glucose values chosen by a client.

Reading Updates

There is no documented public vital-update event yet. Read the export when your interface opens or poll it at a restrained interval such as once per second for a live monitor. The export is a local lookup and does not perform a server callback.

Do not poll every frame. Cache the last values and update your UI only when the numeric data changed. A public update event will be documented here only after it exists in the released resource.

Server-Side Integrations

The public API does not currently expose a server-side patient-profile reader. Do not work around that by:

  • querying or editing ambulance_users.injury_data directly;
  • accepting vital values forwarded by an untrusted client;
  • triggering internal synchronization or treatment events;
  • retaining and mutating internal patient tables.

Those approaches bypass validation and can race the normal treatment and persistence flow. A public server read export must return a read-only numeric snapshot and will be documented only after it is implemented.

Changing Patient State

GetLocalInjuryProfile() is a read API. There is intentionally no public raw "set vital" event.

For custom treatments, use Custom Medications. Its server callbacks receive the authoritative patient and supported mutation helpers. Use the documented revive or heal APIs only when those exact actions are required.

All gameplay mutations must remain server-authoritative and pass the normal permission, distance, item, and treatment checks.

Migrating an Existing Integration

The numeric release is a breaking data-contract change. Update integrations before deploying it:

  1. Apply the current sky_jobs_base/import.sql and sky_ambulancejob/import.sql migrations. The migration requires ambulance_users, sky_jobs_configurator_settings, and sky_ambulance_vital_migration_backup to use InnoDB.
  2. Require profile.vitalSchemaVersion == 2.
  3. Remove comparisons with too_low, low, normal, high, and too_high.
  4. Read numbers directly from profile.vitals.
  5. Read blood pressure from systolic and diastolic.
  6. Apply the documented units and ranges.
  7. Read conditions.cardiacArrest instead of guessing from other values.
  8. Treat baselineVitals and vitalTargets as numeric sets.
  9. Keep returned tables read-only.
  10. Avoid per-frame polling and unnecessary network events.
  11. Keep all writes and gameplay decisions on the server.

On the first resource start, verify the server console reports Numeric vital migration: state=complete with errors=0. A failed or unsafe migration keeps vital profile persistence write-protected; resolve the reported database or data errors before opening the server to players.

Do not add a local legacy fallback. Servers must migrate the Ambulancejob resource, stored patient profiles, Job Configurator values, and dependent integrations as one coordinated major update.

Support

When opening an integration support request, include the Sky Ambulancejob version, whether your code runs on the client or server, and a redacted sample profile.

Join Discord