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

@@ -18,6 +18,14 @@ const IRON := Color(0.30, 0.30, 0.32)
const ASH := Color(0.18, 0.17, 0.16)
const BINDING := Color(0.75, 0.68, 0.48)
const FIBER := Color(0.45, 0.55, 0.22)
const BERRY := Color(0.62, 0.08, 0.16)
const MUSH_CAP := Color(0.72, 0.30, 0.18)
const MUSH_ROAST := Color(0.40, 0.24, 0.12)
const MUSH_STEM := Color(0.88, 0.84, 0.74)
const CLOTH := Color(0.60, 0.50, 0.32)
const SOIL := Color(0.30, 0.22, 0.15)
static var _cache := {}
static func build(key: String) -> ArrayMesh:
@@ -35,6 +43,14 @@ static func build(key: String) -> ArrayMesh:
"campfire": m = _campfire()
"workbench": m = _workbench()
"chest": m = _chest()
"fiber": m = _fiber()
"berries": m = _berries()
"mushroom": m = _mushroom(MUSH_CAP)
"roasted_mushroom": m = _mushroom(MUSH_ROAST)
"torch": m = _torch()
"grave": m = _grave()
"berry_bush": m = _berry_bush(true)
"berry_bush_bare": m = _berry_bush(false)
_:
push_warning("item_meshes: no mesh for '%s', using a stone" % key)
m = _pebble(1, Color.MAGENTA, Vector3.ONE * 0.15)
@@ -158,3 +174,74 @@ static func _chest() -> ArrayMesh:
_box(st, Vector3(sx, 0.36, 0), Vector3(0.07, 0.74, 0.76), IRON)
_box(st, Vector3(0, 0.55, 0.37), Vector3(0.12, 0.14, 0.03), IRON)
return P._commit(st)
static func _fiber() -> ArrayMesh:
var st := P._begin()
var r := _rng(41)
for i in range(7):
var a := r.randf_range(-0.25, 0.25)
var b := Vector3(r.randf_range(-0.03, 0.03), 0.02, r.randf_range(-0.03, 0.03))
P._cylinder(st, b + Vector3(-0.22, 0, 0), b + Vector3(0.22, 0.03 + a * 0.1, a), 0.008, 0.005, 3, P._mul(FIBER, r.randf_range(0.8, 1.15)), r)
_box(st, Vector3(0, 0.03, 0), Vector3(0.05, 0.05, 0.08), CLOTH)
return P._commit(st)
static func _berries() -> ArrayMesh:
var st := P._begin()
var r := _rng(43)
var n := FastNoiseLite.new()
n.seed = 43
for i in range(9):
var c := Vector3(r.randf_range(-0.07, 0.07), 0.03 + r.randf_range(0.0, 0.05), r.randf_range(-0.07, 0.07))
P._blob(st, c, Vector3.ONE * r.randf_range(0.025, 0.035), n, 0.1, P._mul(BERRY, r.randf_range(0.8, 1.2)), r, 0)
P._blob(st, Vector3(0, 0.08, 0), Vector3(0.05, 0.012, 0.03), n, 0.2, P.BUSH, r, 0)
return P._commit(st)
static func _mushroom(cap: Color) -> ArrayMesh:
var st := P._begin()
var r := _rng(47)
var n := FastNoiseLite.new()
n.seed = 47
P._cylinder(st, Vector3(0, -0.02, 0), Vector3(0, 0.1, 0), 0.025, 0.02, 6, MUSH_STEM, r, -1.0, MUSH_STEM)
P._blob(st, Vector3(0, 0.11, 0), Vector3(0.075, 0.04, 0.075), n, 0.15, cap, r, 1)
return P._commit(st)
static func _torch() -> ArrayMesh:
var st := P._begin()
var r := _rng(53)
_handle(st, 0.5, r)
P._cylinder(st, Vector3(0, 0.42, 0), Vector3(0, 0.58, 0), 0.045, 0.05, 6, CLOTH, r, -1.0, ASH)
return P._commit(st)
static func _grave() -> ArrayMesh:
var st := P._begin()
var r := _rng(59)
var n := FastNoiseLite.new()
n.seed = 59
P._blob(st, Vector3(0, 0.0, 0), Vector3(0.5, 0.22, 0.8), n, 0.15, SOIL, r, 1)
_box(st, Vector3(0, 0.55, -0.62), Vector3(0.08, 1.0, 0.06), PLANK_DARK)
_box(st, Vector3(0, 0.8, -0.62), Vector3(0.5, 0.08, 0.06), PLANK_DARK)
for i in range(4):
var c := Vector3(r.randf_range(-0.35, 0.35), 0.15, r.randf_range(-0.6, 0.6))
P._blob(st, c, Vector3.ONE * r.randf_range(0.07, 0.12), n, 0.3, STONE, r, 0)
return P._commit(st)
## World berry bush (not an item). `ripe` adds the berries.
static func _berry_bush(ripe: bool) -> ArrayMesh:
var st := P._begin()
var r := _rng(61)
var n := FastNoiseLite.new()
n.seed = 61
n.frequency = 2.0
var leaf := P._mul(P.BUSH, 1.1)
var blobs: Array[Vector3] = []
for i in range(4):
var c := Vector3(r.randf_range(-0.4, 0.4), r.randf_range(0.3, 0.55), r.randf_range(-0.4, 0.4))
var rad := r.randf_range(0.35, 0.5)
P._blob(st, c, Vector3(rad, rad * 0.8, rad), n, 0.3, P._mul(leaf, r.randf_range(0.85, 1.1)), r, 1)
blobs.append(c + Vector3(0, rad * 0.4, 0))
if ripe:
for c in blobs:
for k in range(6):
var d := Vector3(r.randf_range(-1, 1), r.randf_range(0.2, 1), r.randf_range(-1, 1)).normalized() * 0.4
P._blob(st, c + d, Vector3.ONE * 0.045, n, 0.1, BERRY, r, 0)
return P._commit(st)