EndCore Framework

Survival system

How hunger, thirst, radiation, infection, immunity and temperature tick on the server, what players feel, and how to tune or drive them.

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):

  1. Hunger and thirst
    • Hunger drops by hunger.decay (0.8) and thirst by thirst.decay (1.2), stopping at 0.
    • If hunger is at or below hunger.starvingAt (0), add hunger.damage (5) with cause starvation.
    • If thirst is at or below thirst.dehydratedAt (0), add thirst.damage (7) with cause dehydration.
  2. 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.
  3. Immunity (uses the radiation from step 2)
    • If radiation is at or above radiation.sicknessAt (400), add radiation.damage (4) with cause radiation, and immunity drops by radiation.immunityDrain (1.0).
    • Otherwise immunity recovers by immunity.regen (0.5), up to immunity.max (100).
  4. Infection (only if the player is already infected, above 0)
    • Infection grows by infection.growth (1.0) multiplied by 1 - immunity / immunity.max, up to infection.max (100). Full immunity halts it; zero immunity lets it grow at the full rate.
    • If infection is at or above infection.lethalAt (100), add infection.damage (6) with cause infection.
  5. Temperature
    • Body temperature drifts toward temperature.normal (98.6) by temperature.regulation (0.4).
    • At or below temperature.hypothermiaBelow (95.0), add temperature.damage (5) with cause hypothermia.
    • At or above temperature.hyperthermiaAbove (104.0), add the same damage with cause hyperthermia.

Damage from every cause is added together for that tick.

Note

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

  1. The new values are written with one SetMetadataBulk call for hunger, thirst, radiation, infection, immunity and temperature. The client gets one PlayerData update, and encore:server:onSetMetaData fires once per key.
  2. If there is damage, the server sends encore:client:survivalDamage(damage, causes) to that player.
  3. 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:

PriorityConditionEffect
1Radiation 400 or moreDeathFailMPDark
2Infection 75 or moreDrugsMichaelAliensFightIn
3Hunger or thirst 5 or lessDeathFailOut
4Temperature 95 or less, or 104 or moreChopVision
5Radiation 250 or more, or infection 40 or moreDrugsTrevorClownsFightIn

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.

Warning

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. 0 or nil clears 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.

KeyDefaultWhat it does
enabledtrueTurns the tick on or off
tickInterval60000Milliseconds between ticks
hunger.decay0.8Hunger lost per tick
hunger.starvingAt0At or below this, starvation damage applies
hunger.damage5Starvation damage per tick
thirst.decay1.2Thirst lost per tick
thirst.dehydratedAt0At or below this, dehydration damage applies
thirst.damage7Dehydration damage per tick
radiation.max1000Highest radiation value
radiation.decay0.5Radiation purged per tick outside a zone
radiation.sicknessAt400At or above this, radiation damage applies and immunity drains
radiation.damage4Radiation damage per tick
radiation.immunityDrain1.0Immunity lost per tick while sick
infection.max100Highest infection value
infection.growth1.0Infection growth per tick at zero immunity
infection.lethalAt100At or above this, infection damage applies
infection.damage6Infection damage per tick
immunity.max100Highest immunity value
immunity.regen0.5Immunity recovered per tick when not irradiated
temperature.normal98.6The temperature the body drifts toward (°F)
temperature.regulation0.4Degrees drifted toward normal per tick
temperature.hypothermiaBelow95.0At or below this, cold damage applies
temperature.hyperthermiaAbove104.0At or above this, heat damage applies
temperature.damage5Cold 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.

lua
-- 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.

ExportArgumentsReturns
AddRadiation(source, amount, reason?)source, amount, reasonboolean
RemoveRadiation(source, amount, reason?)source, amount, reasonboolean
SetRadiation(source, amount, reason?)source, amount, reasonboolean (true if the player is online)
GetRadiation(source)sourcenumber or nil
SetRadiationExposure(source, rate)source, ratePerTicknothing
GetRadiationExposure(source)sourcenumber
AddInfection(source, amount, reason?)source, amount, reasonboolean
RemoveInfection(source, amount, reason?)source, amount, reasonboolean
SetInfection(source, amount, reason?)source, amount, reasonboolean
GetInfection(source)sourcenumber or nil
AddImmunity(source, amount, reason?)source, amount, reasonboolean
RemoveImmunity(source, amount, reason?)source, amount, reasonboolean
GetImmunity(source)sourcenumber or nil
SetTemperature(source, amount, reason?)source, amount, reasonboolean
GetTemperature(source)sourcenumber or nil
AddHunger(source, amount)source, amountboolean
RemoveHunger(source, amount)source, amountboolean
AddThirst(source, amount)source, amountboolean
RemoveThirst(source, amount)source, amountboolean
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

SideEventPayload
Serverencore:server:onRadiationChangesource, newValue, action, reason
Serverencore:server:onInfectionChangesource, newValue, action, reason
Serverencore:server:onImmunityChangesource, newValue, action, reason (add or remove only)
Serverencore:server:onTemperatureChangesource, temperature, 'set', reason
Serverencore:server:onSurvivalTicksource, stats, damage, causes
Client (net)encore:client:onRadiationChangenewValue, action, reason
Client (net)encore:client:onInfectionChangenewValue, action, reason
Client (net)encore:client:onImmunityChangenewValue, action, reason
Client (net)encore:client:onTemperatureChangenewValue, action, reason
Client (net)encore:client:survivalDamagedamage, causes (en-core applies it)
Client (net)encore:client:radiationExposureratePerTick
Client (local)encore:client:survivalDamageddamage, 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:

lua
exports['en-core']:AddInfection(source, 15, 'Bitten')

A RadAway item:

lua
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:

lua
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:

lua
exports['en-core']:SetRadiationExposure(source, 12) -- 12 rads per tick
exports['en-core']:SetRadiationExposure(source, 0)  -- left the zone

Cold weather at night:

lua
local temp = exports['en-core']:GetTemperature(source)
if temp then
    exports['en-core']:SetTemperature(source, temp - 1.5, 'Freezing night')
end

Reacting to the tick:

lua
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:

lua
RegisterNetEvent('encore:client:radiationExposure', function(rate)
    if rate and rate > 0 then
        encore.notify({ description = 'Geiger counter is clicking.', type = 'warning' })
    end
end)