Staying alive in EndCore takes more than dodging zombies. Characters get hungry and thirsty, soak up radiation in hot zones, catch infections and can freeze or overheat. When a stat gets bad enough it hurts them, and the screen shows it.
The work is split in two:
- The server (
server/survival.lua) owns the numbers. It runs a tick that updates every player's stats and decides how much damage they take. - The client (
client/survival.lua) applies that damage to the ped and shows screen effects. Ped health lives on the client under OneSync.
The tick
When survival.enabled is true, the server runs a tick every survival.tickInterval ms (one minute by default). If you disable it, the console prints "Survival simulation disabled by config".
Each tick visits every online player who is not dead and not in last stand. For each one, in this order (defaults in brackets):
- Hunger and thirst
- Hunger drops by
hunger.decay(0.8) and thirst bythirst.decay(1.2), stopping at 0. - If hunger is at or below
hunger.starvingAt(0), addhunger.damage(5) with causestarvation. - If thirst is at or below
thirst.dehydratedAt(0), addthirst.damage(7) with causedehydration.
- Hunger drops by
- Radiation
- If the player has an exposure rate (they are in a radiation zone), radiation rises by that rate, up to
radiation.max(1000). - Otherwise it falls by
radiation.decay(0.5), stopping at 0.
- If the player has an exposure rate (they are in a radiation zone), radiation rises by that rate, up to
- Immunity (uses the radiation from step 2)
- If radiation is at or above
radiation.sicknessAt(400), addradiation.damage(4) with causeradiation, and immunity drops byradiation.immunityDrain(1.0). - Otherwise immunity recovers by
immunity.regen(0.5), up toimmunity.max(100).
- If radiation is at or above
- Infection (only if the player is already infected, above 0)
- Infection grows by
infection.growth(1.0) multiplied by1 - immunity / immunity.max, up toinfection.max(100). Full immunity halts it; zero immunity lets it grow at the full rate. - If infection is at or above
infection.lethalAt(100), addinfection.damage(6) with causeinfection.
- Infection grows by
- Temperature
- Body temperature drifts toward
temperature.normal(98.6) bytemperature.regulation(0.4). - At or below
temperature.hypothermiaBelow(95.0), addtemperature.damage(5) with causehypothermia. - At or above
temperature.hyperthermiaAbove(104.0), add the same damage with causehyperthermia.
- Body temperature drifts toward
Damage from every cause is added together for that tick.
Nothing in en-core pushes temperature away from normal, and nothing infects a player on its own. Weather, zombie bites, clothing and items do that by calling SetTemperature or AddInfection.
After each player's step
- The new values are written with one
SetMetadataBulkcall forhunger,thirst,radiation,infection,immunityandtemperature. The client gets one PlayerData update, andencore:server:onSetMetaDatafires once per key. - If there is damage, the server sends
encore:client:survivalDamage(damage, causes)to that player. encore:server:onSurvivalTick(source, stats, damage, causes)fires on the server.
The per-stat events such as onRadiationChange are not fired by the tick. They only fire when a script changes a stat directly.
How long players last
With the defaults and a one-minute tick, a fresh character reaches 0 hunger after about 125 ticks (roughly 2 hours) and 0 thirst after about 83 ticks (roughly 1 hour 23 minutes). After that, starvation costs 5 health and dehydration 7 health every minute until they eat or drink. Health runs from 0 to 200.
What players see and feel
The client reacts to the server's numbers.
Damage. When encore:client:survivalDamage arrives, the client applies the damage to the ped and flashes a short screen effect. It then fires the local event encore:client:survivalDamaged(damage, causes). The new health reaches the server with the next 10-second health report.
Screen effects. Checked every 2 seconds. Only the worst condition shows:
| Priority | Condition | Effect |
|---|---|---|
| 1 | Radiation 400 or more | DeathFailMPDark |
| 2 | Infection 75 or more | DrugsMichaelAliensFightIn |
| 3 | Hunger or thirst 5 or less | DeathFailOut |
| 4 | Temperature 95 or less, or 104 or more | ChopVision |
| 5 | Radiation 250 or more, or infection 40 or more | DrugsTrevorClownsFightIn |
Movement.
- Sprinting is blocked while hunger or thirst is 5 or less.
- At radiation 400 or more, the player randomly stumbles into a ragdoll while on foot (about 1 in 600 frames).
Effects are cleared when the character unloads and when en-core stops.
The client thresholds above are fixed in client/survival.lua and do not read config.survival. If you change radiation.sicknessAt on the server, the screen effects still start at the numbers in this table.
Radiation zones
A radiation zone is simply a per-player exposure rate. While a player has a rate, the tick adds that much radiation each tick instead of letting it decay.
SetRadiationExposure(source, ratePerTick)sets the rate.0ornilclears it, and natural decay resumes.- Setting it immediately sends
encore:client:radiationExposure(rate)to the player, so a HUD can show a warning at once. GetRadiationExposure(source)returns the current rate.- Exposure is cleared when the character unloads.
Admins can test this with /radzone <player> <rate>. For a full walkthrough of building zones, see Radiation zones.
Configuration
All survival settings are under survival in config/server.lua. Every rate is per tick.
| Key | Default | What it does |
|---|---|---|
enabled | true | Turns the tick on or off |
tickInterval | 60000 | Milliseconds between ticks |
hunger.decay | 0.8 | Hunger lost per tick |
hunger.starvingAt | 0 | At or below this, starvation damage applies |
hunger.damage | 5 | Starvation damage per tick |
thirst.decay | 1.2 | Thirst lost per tick |
thirst.dehydratedAt | 0 | At or below this, dehydration damage applies |
thirst.damage | 7 | Dehydration damage per tick |
radiation.max | 1000 | Highest radiation value |
radiation.decay | 0.5 | Radiation purged per tick outside a zone |
radiation.sicknessAt | 400 | At or above this, radiation damage applies and immunity drains |
radiation.damage | 4 | Radiation damage per tick |
radiation.immunityDrain | 1.0 | Immunity lost per tick while sick |
infection.max | 100 | Highest infection value |
infection.growth | 1.0 | Infection growth per tick at zero immunity |
infection.lethalAt | 100 | At or above this, infection damage applies |
infection.damage | 6 | Infection damage per tick |
immunity.max | 100 | Highest immunity value |
immunity.regen | 0.5 | Immunity recovered per tick when not irradiated |
temperature.normal | 98.6 | The temperature the body drifts toward (°F) |
temperature.regulation | 0.4 | Degrees drifted toward normal per tick |
temperature.hypothermiaBelow | 95.0 | At or below this, cold damage applies |
temperature.hyperthermiaAbove | 104.0 | At or above this, heat damage applies |
temperature.damage | 5 | Cold or heat damage per tick |
The defaults are tuned for a one-minute tick. If you halve tickInterval, halve the rates too to keep the same pace.
-- config/server.lua: a harsher wasteland
survival = {
enabled = true,
tickInterval = 60000,
hunger = { decay = 1.2, starvingAt = 0, damage = 8 },
thirst = { decay = 1.8, dehydratedAt = 0, damage = 10 },
radiation = { max = 1000, decay = 0.25, sicknessAt = 300, damage = 6, immunityDrain = 1.5 },
infection = { max = 100, growth = 1.5, lethalAt = 100, damage = 8 },
immunity = { max = 100, regen = 0.4 },
temperature = { normal = 98.6, regulation = 0.4, hypothermiaBelow = 95.0, hyperthermiaAbove = 104.0, damage = 5 },
},Exports
All survival exports take the player's source first.
| Export | Arguments | Returns |
|---|---|---|
AddRadiation(source, amount, reason?) | source, amount, reason | boolean |
RemoveRadiation(source, amount, reason?) | source, amount, reason | boolean |
SetRadiation(source, amount, reason?) | source, amount, reason | boolean (true if the player is online) |
GetRadiation(source) | source | number or nil |
SetRadiationExposure(source, rate) | source, ratePerTick | nothing |
GetRadiationExposure(source) | source | number |
AddInfection(source, amount, reason?) | source, amount, reason | boolean |
RemoveInfection(source, amount, reason?) | source, amount, reason | boolean |
SetInfection(source, amount, reason?) | source, amount, reason | boolean |
GetInfection(source) | source | number or nil |
AddImmunity(source, amount, reason?) | source, amount, reason | boolean |
RemoveImmunity(source, amount, reason?) | source, amount, reason | boolean |
GetImmunity(source) | source | number or nil |
SetTemperature(source, amount, reason?) | source, amount, reason | boolean |
GetTemperature(source) | source | number or nil |
AddHunger(source, amount) | source, amount | boolean |
RemoveHunger(source, amount) | source, amount | boolean |
AddThirst(source, amount) | source, amount | boolean |
RemoveThirst(source, amount) | source, amount | boolean |
GetAllSurvivalStats(source) | source | { hunger, thirst, stamina, stress, radiation, infection, immunity, temperature, health, armor } or nil |
Rules:
- Add and remove need an amount greater than 0. Results are clamped to each stat's range.
- There is no
SetImmunity. - Temperature is clamped to 70–120 °F.
- Hunger and thirst take no reason and fire no dedicated event; the client just gets a PlayerData update.
The same functions exist on the player object without the source argument, for example player.Functions.AddRadiation(150, 'RadAway'). See Player API.
Admins can set any stat with /survival <player> <stat> <value>, where stat is hunger, thirst, radiation, infection, immunity or temperature.
Events
| Side | Event | Payload |
|---|---|---|
| Server | encore:server:onRadiationChange | source, newValue, action, reason |
| Server | encore:server:onInfectionChange | source, newValue, action, reason |
| Server | encore:server:onImmunityChange | source, newValue, action, reason (add or remove only) |
| Server | encore:server:onTemperatureChange | source, temperature, 'set', reason |
| Server | encore:server:onSurvivalTick | source, stats, damage, causes |
| Client (net) | encore:client:onRadiationChange | newValue, action, reason |
| Client (net) | encore:client:onInfectionChange | newValue, action, reason |
| Client (net) | encore:client:onImmunityChange | newValue, action, reason |
| Client (net) | encore:client:onTemperatureChange | newValue, action, reason |
| Client (net) | encore:client:survivalDamage | damage, causes (en-core applies it) |
| Client (net) | encore:client:radiationExposure | ratePerTick |
| Client (local) | encore:client:survivalDamaged | damage, causes |
newValue is the stat after the change, not the amount added. action is 'add', 'remove' or 'set'. causes is an array of strings: starvation, dehydration, radiation, infection, hypothermia, hyperthermia.
Examples
A zombie bite that infects the player:
exports['en-core']:AddInfection(source, 15, 'Bitten')A RadAway item:
encore.inventory.registerUsable('radaway', function(source, item, slot)
local player = exports['en-core']:GetPlayer(source)
if not player or player.Functions.GetRadiation() <= 0 then return end
if encore.inventory.removeItem(source, 'radaway', 1) then
player.Functions.RemoveRadiation(150, 'RadAway')
end
end)Food that restores hunger:
encore.inventory.registerUsable('canned_beans', function(source, item, slot)
if encore.inventory.removeItem(source, 'canned_beans', 1) then
exports['en-core']:AddHunger(source, 35)
end
end)A hot zone:
exports['en-core']:SetRadiationExposure(source, 12) -- 12 rads per tick
exports['en-core']:SetRadiationExposure(source, 0) -- left the zoneCold weather at night:
local temp = exports['en-core']:GetTemperature(source)
if temp then
exports['en-core']:SetTemperature(source, temp - 1.5, 'Freezing night')
endReacting to the tick:
AddEventHandler('encore:server:onSurvivalTick', function(source, stats, damage, causes)
if damage > 0 then
print(source, damage, table.concat(causes, ', '))
end
end)A HUD warning on the client:
RegisterNetEvent('encore:client:radiationExposure', function(rate)
if rate and rate > 0 then
encore.notify({ description = 'Geiger counter is clicking.', type = 'warning' })
end
end)