Exports & API Reference
Server and client exports, the o-link medical namespace, replicated statebags, and events provided by oxide-medical.
The developer surface of oxide-medical — what other resources can call, read and listen to.
oxide-medical provides the o-link medical namespace. If you are writing a framework-agnostic resource, use that. The direct exports below are the full surface, including the few functions the bridge does not carry.
The Condition Object
Returned everywhere a condition is handed out:
{
id, -- number, database row id
category, -- 'fracture' | 'disease' | 'sickness' | 'wound'
type, -- catalog key, e.g. 'broken_leg_left', 'flu', 'gunshot'
bodypart, -- one of the 15 body parts, or nil
severity, -- 1 minor | 2 moderate | 3 severe | 4 critical
onset, -- when it started
expiresAt, -- when it clears on its own, or nil
data, -- provider pass-through: { weaponHash, class, bullet, source } or nil
}"Active" means not treated and not expired. Every read returns active conditions only.
The fifteen body parts: HEAD, NECK, SPINE, UPPER_BODY, LOWER_BODY, LARM, LHAND, LFINGER, LLEG, LFOOT, RARM, RHAND, RFINGER, RLEG, RFOOT.
Server Exports
local M = exports['oxide-medical']Reading the record
| Export | Returns |
|---|---|
M:GetRecord(src) | { charId, bloodType, dnaHash, immune, allergies, immunities, chronic } or nil |
M:GetBloodType(src) | 'O+' etc., or nil |
M:GetDNA(src) | The DNA hash string, or nil |
M:GetImmunity(src) | 0–100, or nil when no record is loaded |
M:GetOfflineRecord(charId) | { record, conditions } read straight from the database, or nil |
GetOfflineRecord takes a character id string, not a server id, and works whether or not the player is online. It is the right call for an MDT, a medical-records lookup or anything reading a character who is not connected.
Reading conditions
| Export | Returns |
|---|---|
M:GetConditions(src) | Array of active condition objects |
M:HasCondition(src, typeOrCategory) | boolean. Matches either a condition type or a whole category |
M:GetVitals(src) | { pulse, systolic, diastolic, spo2, trend, arrest? }, or nil before the first tick |
if exports['oxide-medical']:HasCondition(src, 'fracture') then ... end
if exports['oxide-medical']:HasCondition(src, 'cardiac_arrest') then ... endChanging conditions
| Export | Returns |
|---|---|
M:AddCondition(src, payload) | The new condition's id, or false |
M:TreatCondition(src, idOrType, treaterSrc?) | boolean |
M:RemoveCondition(src, idOrType) | boolean |
local id = exports['oxide-medical']:AddCondition(src, {
category = 'sickness', -- required
type = 'food_poisoning', -- required
bodypart = nil, -- optional
severity = 2, -- optional, defaults to 1, clamped 1-4
expiresInSec = 540, -- optional; omit for permanent
data = { source = 'my-resource' }, -- optional, stored as JSON
})TreatCondition vs RemoveCondition. Treating marks it treated, records who did it, and grants a disease's immunity window — it is the medical outcome. Removing deletes it with no trace and no immunity — it is a correction. Pick deliberately.
treaterSrc is optional; passing it records that player's character id as the treater.
byEmsOnly does not apply here. That flag only stops a player self-treating with an item. These exports treat anything.
Immunity
| Export | Returns |
|---|---|
M:SetImmunity(src, value) | The clamped value, or false |
M:ModifyImmunity(src, delta) | The clamped value, or false |
Both refresh the player's statebag. Lower immunity means illness takes hold more easily.
Suspension
| Export | Returns |
|---|---|
M:SetExempt(src, bool) | true |
M:IsExempt(src) | boolean |
While exempt, the player's conditions, physical effects and symptoms all blank out (their blood type and immune stat are kept), every trigger and the damage tick skip them, and no wound reports are accepted. Turning it off restores everything immediately.
IsExempt is also true whenever the player's replicated godmode flag is set, with no dependency on the resource that sets it.
Client Exports
exports['oxide-medical']:GetConditions() --> array of condition objects (own player)
exports['oxide-medical']:IsInjured() --> booleanBoth read from the local statebag cache, so they are cheap and safe in a loop.
The o-link Medical Namespace
The framework-agnostic path, and what you should use in a released resource.
Server:
olink.medical.GetResourceName() --> 'oxide-medical', or 'none' when absent
olink.medical.GetRecord(src)
olink.medical.GetBloodType(src)
olink.medical.GetDNA(src)
olink.medical.GetImmunity(src)
olink.medical.GetConditions(src)
olink.medical.GetVitals(src)
olink.medical.HasCondition(src, typeOrCategory)
olink.medical.AddCondition(src, payload)
olink.medical.TreatCondition(src, idOrType, treaterSrc?)
olink.medical.RemoveCondition(src, idOrType)
olink.medical.GetOfflineRecord(charId)Client:
olink.medical.GetConditions()
olink.medical.IsInjured()Not on the bridge: SetImmunity, ModifyImmunity, SetExempt and IsExempt. Call those through the direct exports if you need them.
Detecting whether it is actually installed
olink.supports() is not enough here. The o-link stubs are callable and answer with defaults, so a bare install passes the check and then silently returns nothing.
Gate on the provider name instead:
local function MedicalAvailable()
local fn = olink.medical and olink.medical.GetResourceName
if not fn then return false end
local ok, name = pcall(fn)
return ok and type(name) == 'string' and name ~= '' and name ~= 'none'
endThis is exactly what oxide-ems does before offering any treatment.
Statebags
oxide:medical — the condition view
Replicated, so any client can read it for any player.
Player(src).state['oxide:medical'] -- server
LocalPlayer.state['oxide:medical'] -- client, own player{
bloodType = 'O+',
immune = 100,
conditions = { -- active only, compact form
{ id, category, type, bodypart, severity },
},
flags = {
movementPenalty = 0.0, -- 0-1, worst active fracture
limp = false,
aimSway = false,
bleeding = 0, -- 0-5, summed severity of bleeding wounds
hasBullet = false, -- a retained bullet is present
},
symptoms = { cough = true, queasy = true }, -- aggregate set, O(1) lookup
}Watch it on the client:
AddStateBagChangeHandler('oxide:medical', ('player:%s'):format(cache.serverId), function(_, _, value)
-- value is the view above, or nil
end)flags is the cheapest way to ask "is this player bleeding / limping / carrying a bullet" without a callback. symptoms is a set, not an array — check symptoms.cough, don't iterate looking for it.
oxide:medical:vitals — the vitals feed
A separate bag on purpose. Anything watching oxide:medical re-runs the whole client effects pipeline on change, and a value that ticks every two seconds must not live there.
{
pulse = 76, -- bpm, quantized
systolic = 120, -- mmHg, quantized
diastolic = 80,
spo2 = 98, -- %, quantized
trend = 'steady', -- 'rising' | 'falling' | 'steady'
arrest = true, -- present only during cardiac arrest; absent otherwise
}Published only when a quantized value actually changes, so an idle player costs nothing. nil before the first tick after login, and cleared on disconnect.
A dead player reads 0/0/0 with spo2 = 0 — a flatline.
Events
Condition changes
AddEventHandler('oxide:medical:conditionAdded', function(src, condition) end)
AddEventHandler('oxide:medical:conditionTreated', function(src, conditionId) end)
AddEventHandler('oxide:medical:conditionRemoved', function(src, conditionId) end)
AddEventHandler('oxide:medical:conditionUpdated', function(src, condition) end)conditionUpdated fires when severity or data changes on an existing condition — disease progression is the main source.
Server-side only.
The o-link relay
One event carrying all four, for consumers that would rather subscribe once:
AddEventHandler('olink:server:medicalConditionChanged', function(src, data)
-- data = { action = 'added'|'treated'|'removed'|'updated', condition = { ... } }
end)This is what oxide-ems listens to for its triage state.
Blood evidence
AddEventHandler('oxide:medical:bleed', function(src, data)
-- data = { coords, charId, dnaHash, bodypart, severity }
end)
AddEventHandler('olink:server:medicalBleed', function(src, data) end) -- same payloadFires when a player bleeds from a non-firearm source: falls, fire, blades and animal attacks. Rate-limited per player.
Firearm and melee blood is deliberately excluded — oxide-police drops that itself from the weapon-damage event, and emitting here as well would create the evidence twice.
Internal events — do not trigger these
These carry the resource's own state between its server and client sides. They validate their input, so triggering them from outside will not corrupt anything, but they are not an API:
oxide:medical:applyDamage, oxide:medical:treatStart, oxide:medical:reportWound, oxide:medical:treatComplete, oxide:medical:treatCancel, oxide:medical:reportContagion, oxide:medical:reportExposure.
Usable Items
Every distinct cure item across all four catalogs is registered as a usable item through o-link at startup. Using one runs a progress bar and, on completion, treats the worst-severity active condition that item cures. The server re-validates the item and the condition before consuming anything.
Items whose only match is a medic-only condition tell the player a medic is needed rather than being consumed.
Registering the same item name yourself elsewhere will conflict — check the catalogs first.
Adding Your Own Conditions
You do not need code. Add an entry to a catalog in shared/config/ and everything else follows: persistence, the statebag view, symptoms, notifications, treatment, damage over time, the admin tools and the exports all read from the catalogs.
The one thing that will not happen automatically is a new symptom token having a visible effect — map it in shared/config/symptoms.lua, or it is silently ignored.
If you add a condition from another resource via AddCondition with a type that is not in any catalog, it works — it persists, appears in the statebag, shows in /med record and can be treated — but it has no symptoms, no cure item and no notification pool. Add a catalog entry to give it those.