Skip to content

Needs and Physical State

The ambient systems that run whether or not the player is doing anything.

Sources: plugins/hunger/, plugins/stamina/, plugins/flashlight/, metatables/sv_player.lua, libraries/sv_player.lua (the tick), sh_config.lua.


1. The tick

There is exactly one timer driving everything (libraries/sv_player.lua):

every 0.1 s  →  PlayerTenthSecond(player)   for every player
every 1.0 s  →  PlayerSecond(player)        for every player
                + unconditional auto-CSVar broadcast

Two rates, both per-player, both fired as hooks so plugins can attach. Simple and effective. The auto-CSVar broadcast riding along on the 1 s tick is D-11.

Design note: two fixed rates for all ambient simulation is a good default and worth keeping. The new implementation should make the rate a property of the need, since temperature and disease do not need 1 Hz.


2. Hunger

plugins/hunger/. The only need in the original with real consequences.

Setting Default Meaning
Hunger Minutes 30 Minutes from empty to full hunger
Hunger Damage 5 Damage per second while starving
Hunger Death true Starvation can kill

Semantics — note the polarity

_Hunger.amount runs 0 → 100 where 100 is starving. Eating reduces it. The HUD bar therefore fills as you get hungrier. Worth flagging because every instinct says otherwise, and the reimplementation should almost certainly invert it to a satiation value.

Per second: amount += 1 / (HungerMinutes × 0.6), clamped to [0, 100].

Damage

At amount == 100:

  • Knocked out → the ragdoll takes the damage, not the player.
  • In a vehicle → double damage. The source comment reads: "cars are dicks".
  • Otherwise → normal damage.

The vehicle penalty is deliberate anti-AFK design: you cannot park up and idle safely.

Feeding

items/base/food.lua: onUse refuses if amount < 25"You do not need any more food!" — otherwise reduces hunger by the item's Hunger value. You cannot stockpile satiation, so food must be found repeatedly rather than eaten in bulk once.

Suicide and respawn

Deliberately anti-exploit, and the whole reason the plugin tracks lastTeam and suicided:

if (suicided OR hunger was 100) AND team unchanged:
        respawn with hunger = 50 and health = 50
else:   respawn with hunger = 0

You cannot clear starvation by killing yourself — you come back half-starved and at half health. Changing team does reset it, which is a deliberate loophole: taking a new job is a fresh start. Excellent, cheap, targeted design. Preserve the behaviour exactly.

Bar suppression

If the player is dead, arrested or tied, hunger is reported to the client as 0 — hiding the bar rather than adding a "hide" flag. A UI concern solved by lying in the data layer; preserve the intent (no hunger bar in jail), not the method.


3. Stamina

plugins/stamina/. Runs on the 0.1 s tick.

Setting Default
Stamina Drain 0.35 per 0.1 s while sprinting
Stamina Restore 0.15 per 0.1 s otherwise
Jump cost 5 flat

Drain is ~2.3× restore, so sprinting is a resource with a real recovery cost.

Injury coupling

Below 50 health, both rates worsen proportionally:

drain   += (50 - health) × 0.05      -- at 1 hp: +2.45, i.e. 8× the base rate
restore -= (50 - health) × 0.0025    -- at 1 hp: 0.0275, i.e. 5× slower

A wounded player cannot run and cannot recover. There is no separate "injury" system in the original — this is it, and it is entirely emergent from two lines of arithmetic. Elegant, and a good model for the new medical system: injury should degrade capability continuously, not toggle a state.

Speed scaling

Movement speed is interpolated by remaining stamina, not gated:

runSpeed  = lerp(WalkSpeed → RunSpeed,          stamina/100)   -- 150 → 275
walkSpeed = lerp(IncapacitatedSpeed → WalkSpeed, stamina/100)  -- 100 → 150

You slow down smoothly as you tire. No cliff edge.

Exhaustion, with hysteresis

  • Stamina ≤ 1Incapacitate() and set NWBool "Exausted" (sic — the typo is in the source and in the networked variable name).
  • While exhausted, recovery to 50 does nothing — the state persists.
  • Above 50 → exhaustion clears and speeds resume.

A 1 / 50 hysteresis band: collapsing is instant, recovering takes you to half stamina. Deliberate — the comment reads "If you get exausted, it takes a while to wear off. ;)"

The plugin also implements PlayerCanBeRecapacitated to return false while exhausted, so nothing else in the game can stand you up early. This is the cleanest example in the codebase of a plugin extending a core mechanic through a declared gate rather than patching it — the model for the new event bus.

Suppression

Stamina does not run while arrested, tied, holding an entity, or in noclip.


4. Flashlight

plugins/flashlight/ — battery-limited, drains with use, drawn on the shared DrawBottomBars hook. Turned off automatically by Arrest and TieUp. A small system, but it establishes the pattern: a resource attached to a tool, with a HUD bar.


5. Physical states

Six distinct states, all built on the Incapacitate primitive. Full details in 06_CRIME_AND_POLICE.md; summarised here because they belong to the character state model.

State Set by Speed Weapons Escapable
Incapacitated primitive 100 / 100, no jump via Recapacitate (gated)
Exhausted stamina ≤ 1 incapacitated at stamina > 50
Knocked out KnockOut(30 s) ragdolled player input after the timer
Tied TieUp (5 s) incapacitated stored, returned 1 struggle, 30 s meter
Arrested Arrest(300 s) incapacitated confiscated, items lost timer only
Sleeping /sleep, 5 s still player input

_Sleeping deserves a mention: a player who stands still for Sleep Waiting Time (5 s) can deliberately fall asleep — and sleeping regenerates health at 1 HP every 2 seconds (libraries/sv_player.lua, in the 1 s branch of the tick; the two-second rate is a workaround because "the game doesn't like fractions"). The ragdoll's health is kept in sync as it heals.

So resting is the game's only passive healing, and it costs you your ability to react. That is a real mechanic dressed as an affordance, and it should be preserved as one — it gives /sleep a reason to exist beyond flavour, and it means a wounded player has to choose between recovering and staying alert.

Other work on the same tick

Three more behaviours ride the 1 s branch and belong in the character state model:

  • Stuck detection — sets _StuckInWorld if the player is not IsInWorld() or a trace straight up hits sky (i.e. they are behind the world).
  • Idle kickAutokick time defaults to 15 minutes of inactivity.
  • _HideHealthEffects clears automatically above 50 HP — the "paracetamol" screen effect suppression expires on its own.

6. What does not exist

Against the master plan's Phase 10:

Need Legacy status
Hunger ✅ Full implementation
Stamina ✅ Full implementation (not in the master plan's list, but present)
Thirst Absent — despite a 9-item drinks category. Drinks derive from food and restore hunger.
Sleep ⚠️ No sleep need, but sleeping is the only passive healing (1 HP / 2 s)
Temperature ❌ Absent
Injuries ⚠️ Emergent only, via the stamina/health coupling
Disease ❌ Absent
Bleeding ⚠️ Exists as meta:Bleed, but it is cosmetic (decals) and its timer is buggy
Healing ⚠️ Consumable items only (drugs category)
Movement penalties ✅ Stamina-scaled speed
Visual effects ⚠️ Blood decals; _HideHealthEffects
Audio effects ❌ Absent

So: one real need, one excellent hidden one, and everything else is new work.

The important lesson is not the gap list but how the two implemented needs are built:

  1. A single value on a fixed tick.
  2. Consequences that scale continuously rather than switching.
  3. Hysteresis on any state that is unpleasant to enter.
  4. An anti-exploit rule at the respawn boundary.
  5. A declared gate so other systems can veto recovery.
  6. A HUD bar through one shared drawing hook.

That is the specification for the generic needs framework. Thirst, temperature and disease should be configurations of it, not new code.


7. What survives

Survives: the two-rate tick; hunger's rate, damage and vehicle penalty; the refuse-to-eat threshold; the suicide/respawn anti-exploit including the change-team loophole; stamina's drain/restore asymmetry; injury coupling to both rates; smooth speed interpolation; the 1/50 exhaustion hysteresis; PlayerCanBeRecapacitated as a gate; needs suppressed while arrested or tied; the shared bottom-bar HUD; sleeping as the only passive healing; idle kick and stuck-in-world detection.

Does not survive: hunger's inverted polarity; needs living in plugin-private ply._Xxx tables; needs never being persisted (hunger resets on every join); zeroing a value to hide its UI; the Exausted misspelling on the wire; the bleed timer bug.