Wolves with a den, fishing, and a mixed diet

Wolves
- Den: a boulder cave at the foot of the rim, sited from the seed (away
  from the sea and spawn, mouth toward the valley, clearing in front)
- Grace period: no wolves days 1-2; they move in on day 3 and howl; hunting
  from night 4; pack of 2, 3 from day 6, 4 from day 9; replaced by day
- Wary pack hunters: circle and dart in one at a time; a lit campfire is a
  safe zone, a torch makes them hesitate, walls block bites (line of
  sight); hit one and the pack turns; hurt wolves flee; a lone wolf is
  timid; by day they defend the den; home at dawn
- Glowing eyes, synthesised howls, can't sleep with wolves prowling
- Wolf pelt and Wolf Cloak (+25 warmth)
- Animals take a committed detour around obstacles

Fishing
- Fishing rod (workbench); cast a float onto the lake, 1.5 s to land a bite
- Perch and rarer trout; faster bites at dawn/dusk and in rain; grill them
- Visible fish near the shore that jump with splash rings

Mixed diet
- Foods belong to meat, fish or greens; each level runs down over ~1.5 days
- One group low: no healing; two low: Malnourished (health drains);
  all three good: Balanced diet (heal x1.5); HUD diet bars; saved

Tests: test_wolves and test_fishing_diet; run_all.sh covers 9 suites
This commit is contained in:
Paul Bailey
2026-11-04 11:32:16 +00:00
parent 67bf85523d
commit f174221268
23 changed files with 2069 additions and 21 deletions

View File

@@ -65,6 +65,14 @@ static func build(key: String) -> ArrayMesh:
"bow": m = _bow()
"arrow": m = _arrow()
"hide_cloak": m = _cloak()
"wolf_pelt": m = _pelt(WOLF_FUR)
"wolf_cloak": m = _cloak(WOLF_FUR)
"fishing_rod": m = _rod()
"bobber": m = _bobber()
"perch": m = _fish(PERCH, 0.16, false)
"trout": m = _fish(TROUT, 0.24, false)
"grilled_perch": m = _fish(PERCH, 0.16, true)
"grilled_trout": m = _fish(TROUT, 0.24, true)
"berry_bush": m = _berry_bush(true)
"berry_bush_bare": m = _berry_bush(false)
_:
@@ -458,12 +466,74 @@ static func _arrow() -> ArrayMesh:
P._tri(st, Vector3(0, 0, 0.33), Vector3(0, 0, 0.2), d + Vector3(0, 0, 0.3), Vector3(d.y, -d.x, 0), FLETCH)
return P._commit(st)
static func _cloak() -> ArrayMesh:
const WOLF_FUR := Color(0.5, 0.5, 0.52)
static func _pelt(col: Color) -> ArrayMesh:
var st := P._begin()
var r := _rng(149)
_box(st, Vector3(0, 0.02, 0), Vector3(0.42, 0.035, 0.24), col, Basis(Vector3.UP, 0.15))
_box(st, Vector3(0.04, 0.05, 0.01), Vector3(0.3, 0.035, 0.18), _board(r, col), Basis(Vector3.UP, -0.25))
_box(st, Vector3(-0.2, 0.03, 0.05), Vector3(0.12, 0.03, 0.05), P._mul(col, 0.8), Basis(Vector3.UP, 0.7)) # tail
return P._commit(st)
static func _cloak(col: Color = HIDE) -> ArrayMesh:
var st := P._begin()
var r := _rng(139)
# Folded cloak: a stack of fur with a bone clasp.
for i in range(3):
_box(st, Vector3(0, 0.03 + i * 0.045, 0), Vector3(0.42 - i * 0.05, 0.04, 0.3 - i * 0.03), _board(r, HIDE), Basis(Vector3.UP, (i - 1) * 0.08))
_box(st, Vector3(0, 0.17, 0.0), Vector3(0.32, 0.05, 0.08), P._mul(FUR, 1.1))
_box(st, Vector3(0, 0.03 + i * 0.045, 0), Vector3(0.42 - i * 0.05, 0.04, 0.3 - i * 0.03), _board(r, col), Basis(Vector3.UP, (i - 1) * 0.08))
_box(st, Vector3(0, 0.17, 0.0), Vector3(0.32, 0.05, 0.08), P._mul(FUR if col == HIDE else col, 1.1))
_box(st, Vector3(0.1, 0.2, 0.05), Vector3(0.05, 0.03, 0.03), BONE)
return P._commit(st)
# --- fishing ------------------------------------------------------------------
const PERCH := Color(0.45, 0.52, 0.30)
const TROUT := Color(0.50, 0.55, 0.58)
const GRILLED := Color(0.55, 0.36, 0.18)
const FLOAT_RED := Color(0.85, 0.15, 0.12)
## Rod along +Y from the grip, a tip ring at the top. Rod tip is at
## (0, ROD_TIP, 0) (hands.gd draws the line from there).
const ROD_TIP := 1.15
static func _rod() -> ArrayMesh:
var st := P._begin()
var r := _rng(151)
P._cylinder(st, Vector3(0, -0.15, 0), Vector3(0, ROD_TIP, 0), 0.02, 0.006, 5, HANDLE, r, -1.0, HANDLE)
_box(st, Vector3(0, 0.0, 0), Vector3(0.05, 0.16, 0.05), BINDING)
# A simple wooden reel with line wound on it.
P._cylinder(st, Vector3(-0.05, 0.12, 0.04), Vector3(0.05, 0.12, 0.04), 0.045, 0.045, 7, PLANK_DARK, r, 0.0, LINEN)
return P._commit(st)
static func _bobber() -> ArrayMesh:
var st := P._begin()
var r := _rng(157)
var n := FastNoiseLite.new()
# Big enough to spot 10 m out on the water.
P._blob(st, Vector3(0, 0.03, 0), Vector3(0.08, 0.06, 0.08), n, 0.0, Color(0.95, 0.95, 0.92), r, 1)
P._blob(st, Vector3(0, 0.09, 0), Vector3(0.07, 0.05, 0.07), n, 0.0, FLOAT_RED, r, 1)
P._cylinder(st, Vector3(0, 0.11, 0), Vector3(0, 0.24, 0), 0.01, 0.007, 4, FLOAT_RED, r)
return P._commit(st)
## A fish lying on its side along X, head at +X. `len` is half its length.
static func _fish(col: Color, len: float, grilled: bool) -> ArrayMesh:
var st := P._begin()
var r := _rng(163 + int(len * 100.0))
var n := FastNoiseLite.new()
n.seed = 163
var body := GRILLED.lerp(col, 0.25) if grilled else col
P._blob(st, Vector3(0, len * 0.3, 0), Vector3(len, len * 0.32, len * 0.14), n, 0.05, body, r, 1)
P._blob(st, Vector3(len * 0.1, len * 0.22, 0), Vector3(len * 0.75, len * 0.18, len * 0.12), n, 0.05,
P._mul(body, 1.25) if not grilled else P._mul(body, 0.8), r, 1) # pale belly / char
# Tail fin.
var t0 := Vector3(-len * 0.9, len * 0.3, 0)
var fin := P._mul(body, 0.8)
P._tri(st, t0, Vector3(-len * 1.35, len * 0.65, 0), Vector3(-len * 1.35, -len * 0.05, 0), Vector3(0, 0, 1), fin)
P._tri(st, t0, Vector3(-len * 1.35, len * 0.65, 0), Vector3(-len * 1.35, -len * 0.05, 0), Vector3(0, 0, -1), fin)
# Dorsal fin and an eye.
P._tri(st, Vector3(len * 0.3, len * 0.58, 0), Vector3(-len * 0.3, len * 0.58, 0), Vector3(-len * 0.1, len * 0.85, 0), Vector3(0, 0, 1), fin)
P._tri(st, Vector3(len * 0.3, len * 0.58, 0), Vector3(-len * 0.3, len * 0.58, 0), Vector3(-len * 0.1, len * 0.85, 0), Vector3(0, 0, -1), fin)
if not grilled:
for s in [-1.0, 1.0]:
P._blob(st, Vector3(len * 0.75, len * 0.38, s * len * 0.11), Vector3.ONE * len * 0.06, n, 0.0, Color(0.05, 0.05, 0.05), r, 0)
return P._commit(st)