Files
Cinderwild/scripts/creatures/wolf.gd
Paul Bailey f174221268 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
2026-11-04 11:32:16 +00:00

164 lines
4.9 KiB
GDScript

extends "res://scripts/creatures/creature.gd"
## A wolf: a member of the pack that lives in the den (wolf_pack.gd decides
## what the pack is doing; this decides what one wolf does about it).
##
## Wary pack hunters rather than berserkers:
## - In the den by day they rest, but they defend it. Come within 14 m and
## they get up and watch you; within 8 m (or hit one) and they attack.
## - At night the pack stalks you. They circle at a distance and take turns
## darting in to bite, one wolf at a time.
## - Fire keeps them back: near a lit campfire they circle wide and never
## close in. A held torch makes them hesitate.
## - They can't bite through walls (a line-of-sight check), so a closed hut
## is safe.
## - A wolf on its own is timid: it keeps its distance and backs off.
## - A badly hurt wolf runs, and comes back another night.
var pack: Node
var slot := 0
var wounded := false ## ran off hurt: stays away until dawn
func display_name() -> String:
return "Wolf"
func interact_prompt(p: Node) -> String:
if is_dead():
return "Wolf carcass [E] Butcher"
var s := "Wolf"
match state:
"rest": s = "Wolf (resting)"
"alert", "stalk": s = "Wolf (watching you)"
"lunge", "defend": s = "Wolf (attacking!)"
if not Actions.melee_stats(p).armed and not ItemDB.get_item(p.held_item_id()).has("bow"):
s += " (you need a weapon)"
return s
func _think(dt: float) -> void:
var pp: Vector3 = player.global_position
var d := global_position.distance_to(pp)
_state_t -= dt
if state == "flee":
var away := global_position - _threat
steer(atan2(-away.x, -away.z))
if _state_t <= 0.0 and d > 30.0:
state = "home"
return
if wounded and pack.mode == "hunt":
_go_to(pack.home_slot(self))
state = "home"
return
match pack.mode:
"den":
_think_den(pp, d)
"hunt":
if _aggro > 0.0 and d < 25.0:
_attack(pp, d, "defend")
elif pack.alive_count() <= 1:
_think_alone(pp, d)
else:
_think_hunt(pp, d)
_:
_think_den(pp, d)
## Daytime (and the howling night before hunts begin): rest at the den,
## defend it if you come too close.
func _think_den(pp: Vector3, d: float) -> void:
var den_d: float = pack.den_pos.distance_to(pp)
if (_aggro > 0.0 or den_d < pack.DEFEND_RADIUS) and d < 25.0:
_attack(pp, d, "defend")
return
if den_d < pack.ALERT_RADIUS:
state = "alert"
var to := pp - global_position
target_yaw = atan2(-to.x, -to.z)
return
var home: Vector3 = pack.home_slot(self)
if Vector2(home.x - global_position.x, home.z - global_position.z).length() > 1.2:
state = "home"
_go_to(home)
else:
state = "rest"
target_yaw = pack.den_yaw + (slot - 1.5) * 0.6
## Night, with the pack: circle, and dart in when it's your turn.
func _think_hunt(pp: Vector3, d: float) -> void:
match state:
"lunge":
# Break off first if you've reached a fire or it's taking too long.
if _state_t <= 0.0 or not pack.may_close_in():
state = "stalk"
pack.lunge_done(self)
else:
_attack(pp, d, "lunge")
"retreat":
var away := global_position - pp
steer(atan2(-away.x, -away.z))
if _state_t <= 0.0:
state = "stalk"
_:
state = "stalk"
_go_to(pack.circle_point(self))
if pack.request_lunge(self):
state = "lunge"
_state_t = 4.0
## The last wolf of a pack keeps its distance and won't start a fight.
func _think_alone(pp: Vector3, d: float) -> void:
if d < 10.0:
_threat = pp
state = "flee"
_state_t = 3.0
return
state = "stalk"
_go_to(pp + (global_position - pp).normalized() * 18.0)
## Run at the player and bite when in range with nothing in between.
func _attack(pp: Vector3, d: float, as_state: String) -> void:
state = as_state
var to := pp - global_position
steer(atan2(-to.x, -to.z))
if d < def.attack_range and _attack_cd <= 0.0 and _can_reach_player():
_attack_cd = def.attack_cd
player.survival.damage(def.damage, global_position)
if as_state == "lunge":
state = "retreat"
_state_t = 1.6
pack.lunge_done(self)
## No wall, door or rock between the wolf's jaws and the player.
func _can_reach_player() -> bool:
var from := global_position + Vector3(0, 0.8, 0)
var to: Vector3 = player.global_position + Vector3(0, 1.0, 0)
var q := PhysicsRayQueryParameters3D.create(from, to, 1)
q.exclude = [get_rid(), player.get_rid(), terrain.get_node("TerrainBody").get_rid()]
return get_world_3d().direct_space_state.intersect_ray(q).is_empty()
func _go_to(p: Vector3) -> void:
var to := p - global_position
to.y = 0.0
if to.length() > 0.3:
steer(atan2(-to.x, -to.z))
_goal_d = to.length()
var _goal_d := 0.0
func _desired_speed() -> float:
match state:
"rest", "alert": return 0.0
"home": return 2.2 if _goal_d < 8.0 else 6.0
"stalk": return 0.0 if _goal_d < 1.0 else (3.4 if _goal_d < 7.0 else 7.5)
"lunge", "defend": return def.run
"retreat": return 6.0
"flee": return def.run
return 0.0
func _react(from: Vector3) -> void:
pack.alert(from)
if hp < def.hp * 0.4:
wounded = true
_threat = from
state = "flee"
_state_t = 6.0
else:
_aggro = 20.0