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

@@ -12,6 +12,7 @@ signal prompt_changed(text: String)
const Campfire = preload("res://scripts/building/campfire.gd")
const BuildPiece = preload("res://scripts/building/build_piece.gd")
const ItemMeshesScript = preload("res://scripts/items/item_meshes.gd")
const REACH := 3.2
const PLACE_REACH := 6.0
const SWING_TIME := 0.5
@@ -173,8 +174,24 @@ func _physics_process(delta: float) -> void:
_set_prompt("Drawing %d%%" % roundi(draw_power() * 100.0) if _drawing else "[hold LMB] Draw (%d arrows)" % n)
elif def.get("weapon", {}).has("throw"):
_set_prompt("[LMB] Thrust [RMB] Throw")
elif def.has("fishing"):
var b: Node3D = player.get_parent().bobber
if b == null or not is_instance_valid(b):
_set_prompt("[LMB] Cast into the lake")
else:
match b.state:
"biting": _set_prompt("Bite! [LMB] Reel in now")
"floating": _set_prompt("Waiting for a bite... [LMB] Reel in")
_: _set_prompt("[LMB] Reel in")
else:
_set_prompt("")
# The line runs from the rod tip; switching away from the rod reels in.
var bob: Node3D = player.get_parent().bobber
if bob != null and is_instance_valid(bob):
if def.has("fishing"):
bob.tip = rod_tip()
else:
player.get_parent().end_fishing()
if _drawing:
if not def.has("bow"):
_drawing = false # switched away mid-draw
@@ -252,9 +269,32 @@ func _unhandled_input(event: InputEvent) -> void:
Actions.notify.emit("Can't place that here")
elif _held_def().has("bow"):
_start_draw()
elif _held_def().has("fishing"):
_fish_click()
else:
swing()
# --- fishing ----------------------------------------------------------------------
func _fish_click() -> void:
var w: Node3D = player.get_parent()
if w.bobber != null and is_instance_valid(w.bobber):
Actions.reel(player, w)
return
if _swing_cd > 0.0:
return
var a := _aim()
if Actions.cast_line(player, rod_tip(), a[1], w):
_swing_cd = 0.6
var tw := create_tween()
var back := Transform3D(_view_rest.basis.rotated(Vector3.RIGHT, deg_to_rad(40)), _view_rest.origin)
tw.tween_property(_view, "transform", back, 0.12)
tw.tween_property(_view, "transform", _view_rest, 0.25).set_trans(Tween.TRANS_SINE)
## World position of the held rod's tip (where the line starts).
func rod_tip() -> Vector3:
return _view.global_transform * Vector3(0, ItemMeshesScript.ROD_TIP, 0)
# --- bow and spear --------------------------------------------------------------
func _aim() -> Array: