extends Node ## Survival stats: health, stamina, hunger (satiety) and warmth, each 0..100. ## ## - Hunger drains over about a day and a quarter, faster when you're cold. ## - Warmth moves toward a target: air temperature (day/night, altitude), ## plus heat from fires and a held torch, minus being wet. ## - Health drains while starving or freezing, and regenerates when fed and warm. ## - Stamina pays for sprinting, jumping and swinging, and regenerates after a ## short pause. ## ## step(dt) holds all the rules, so tests can fast-forward time. In M2 this runs ## on the server. signal changed signal died const MAX := 100.0 const HUNGER_DRAIN := 100.0 / 1500.0 ## full to empty in ~1.25 in-game days const STAMINA_REGEN := 24.0 const STAMINA_DELAY := 1.0 const WET_SECONDS := 90.0 var health := MAX var stamina := MAX var hunger := MAX ## satiety: 100 = full, 0 = starving var warmth := 70.0 var wet := 0.0 ## seconds of "Wet" left var heat := 0.0 ## 0..1 from nearby fires etc (last step) var statuses: Array[String] = [] var dead := false var player: CharacterBody3D var world: Node3D var _stamina_wait := 0.0 func _process(delta: float) -> void: if world != null and not world.menu_mode: step(delta) func step(dt: float) -> void: if dead: return var p := player.global_position heat = world.heat_at(p) var target: float = world.air_warmth(p) + heat * 70.0 if wet > 0.0: target -= 22.0 wet = maxf(wet - dt * (3.0 if heat > 0.3 else 1.0), 0.0) # fires dry you off target = clampf(target, 0.0, MAX) # Warm up faster than you cool down: getting to a fire should feel good. warmth = move_toward(warmth, target, (7.0 if target > warmth else 1.6) * dt) hunger = maxf(hunger - HUNGER_DRAIN * dt * (1.5 if warmth < 30.0 else 1.0), 0.0) if _stamina_wait > 0.0: _stamina_wait -= dt else: stamina = minf(stamina + STAMINA_REGEN * dt, MAX) var dmg := 0.0 if hunger <= 0.0: dmg += 0.6 if warmth < 12.0: dmg += 0.5 if dmg > 0.0: health = maxf(health - dmg * dt, 0.0) elif hunger > 40.0 and warmth > 35.0: health = minf(health + 0.5 * dt, MAX) _update_statuses() changed.emit() if health <= 0.0: dead = true died.emit() func _update_statuses() -> void: statuses.clear() if heat > 0.25: statuses.append("Warm") if warmth < 12.0: statuses.append("Freezing") elif warmth < 30.0: statuses.append("Cold") if hunger <= 0.0: statuses.append("Starving") elif hunger < 25.0: statuses.append("Hungry") if wet > 0.0: statuses.append("Wet") ## Spends stamina for an action. Returns false (and spends nothing) if you're ## too tired. Continuous costs (sprinting) pass `partial = true`. func use_stamina(amount: float, partial: bool = false) -> bool: if stamina <= 0.0 or (not partial and stamina < amount * 0.5): return false stamina = maxf(stamina - amount, 0.0) _stamina_wait = STAMINA_DELAY return true func eat(food: Dictionary) -> void: hunger = minf(hunger + float(food.get("hunger", 0)), MAX) health = minf(health + float(food.get("health", 0)), MAX) changed.emit() func soak() -> void: wet = WET_SECONDS func reset_after_death() -> void: dead = false health = 50.0 stamina = MAX hunger = 50.0 warmth = 60.0 wet = 0.0 changed.emit() func to_data() -> Dictionary: return {"health": health, "stamina": stamina, "hunger": hunger, "warmth": warmth, "wet": wet} func load_data(d: Dictionary) -> void: health = float(d.get("health", health)) stamina = float(d.get("stamina", stamina)) hunger = float(d.get("hunger", hunger)) warmth = float(d.get("warmth", warmth)) wet = float(d.get("wet", 0.0)) dead = false changed.emit()