M1b: survival stats, day/night, fire fuel, cooking, foraging, torches, death + grave

- Health, stamina, hunger, warmth and wet with HUD bars, status tags and clock
- 20-minute day/night cycle with moon, stars and lit clouds
- Campfire fuel (2 min start, 15 min max), heat radius, cooking needs a lit fire
- Berry bushes (regrow 10 min), mushrooms, roasted mushroom, torch
- Stamina costs for sprint, jump and harvest hits
- Death respawns at island start and leaves a grave bag
- Saves include stats, time of day, fire fuel, regrowth timers and graves
- New test_survival_stats suite; tests fail if a script error aborts them
This commit is contained in:
Paul Bailey
2026-11-04 01:19:41 +00:00
parent 2b1afc86ba
commit fc7eb3cfed
28 changed files with 1326 additions and 97 deletions

View File

@@ -132,7 +132,7 @@ func close() -> void:
if left > 0 and container != null:
left = container.inventory.add(_cursor.id, left)
_cursor = null
if container != null and container.inventory.changed.is_connected(_refresh):
if container != null and is_instance_valid(container) and container.inventory.changed.is_connected(_refresh):
container.inventory.changed.disconnect(_refresh)
container = null
visible = false
@@ -147,12 +147,17 @@ func _input(event: InputEvent) -> void:
get_viewport().set_input_as_handled()
elif event is InputEventMouseMotion:
_cursor_view.global_position = event.position
# Walking away from a chest closes it.
if container != null and player.global_position.distance_to(container.global_position) > 5.0:
close()
func _process(_d: float) -> void:
if visible and container != null and player.global_position.distance_to(container.global_position) > 5.0:
if not visible or container == null:
return
# Containers can vanish while open (an emptied grave crumbles).
if not is_instance_valid(container) or container.is_queued_for_deletion():
container = null
_rebuild_chest()
return
if player.global_position.distance_to(container.global_position) > 5.0:
close()
func _rebuild_chest() -> void:
@@ -177,7 +182,7 @@ func _on_player_slot(i: int, right: bool, shift: bool) -> void:
_refresh()
func _on_chest_slot(i: int, right: bool, shift: bool) -> void:
if not Actions._in_reach(player, container, 1.0):
if container == null or not is_instance_valid(container) or not Actions._in_reach(player, container, 1.0):
return
if shift:
Actions.transfer(player, container.inventory, i, player.inventory, container)