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:
@@ -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:
|
||||
|
||||
@@ -42,6 +42,35 @@ var _hurt_cd := 0.0 ## no natural healing for a few seconds after a hit
|
||||
signal hurt(amount: float, from: Vector3)
|
||||
const HURT_NO_REGEN := 6.0
|
||||
|
||||
## Mixed diet: each food group (meat, fish, greens) has a level that runs
|
||||
## down over about a day and a half. Food from a group tops it up.
|
||||
## - One group low: you don't heal naturally ("Poor diet").
|
||||
## - Two or more low: Malnourished. Health slowly drains, stamina recovers
|
||||
## slower. Living on berries alone will kill you.
|
||||
## - All three kept up: Balanced diet, healing x1.5.
|
||||
const DIET_GROUPS := ["meat", "fish", "plants"]
|
||||
const DIET_NAMES := {"meat": "meat", "fish": "fish", "plants": "greens"}
|
||||
const DIET_DRAIN := 100.0 / 1800.0
|
||||
const DIET_LOW := 15.0
|
||||
const DIET_GOOD := 40.0
|
||||
const DIET_START := 60.0
|
||||
const MALNOURISHED_DRAIN := 0.12
|
||||
var diet := {"meat": DIET_START, "fish": DIET_START, "plants": DIET_START}
|
||||
|
||||
## The groups that are running low right now.
|
||||
func diet_lacking() -> Array:
|
||||
var out := []
|
||||
for g in DIET_GROUPS:
|
||||
if float(diet[g]) < DIET_LOW:
|
||||
out.append(g)
|
||||
return out
|
||||
|
||||
func balanced_diet() -> bool:
|
||||
for g in DIET_GROUPS:
|
||||
if float(diet[g]) < DIET_GOOD:
|
||||
return false
|
||||
return true
|
||||
|
||||
var player: CharacterBody3D
|
||||
var world: Node3D
|
||||
var _stamina_wait := 0.0
|
||||
@@ -85,7 +114,13 @@ func step(dt: float) -> void:
|
||||
if _stamina_wait > 0.0:
|
||||
_stamina_wait -= dt
|
||||
else:
|
||||
stamina = minf(stamina + STAMINA_REGEN * (1.5 if rested > 0.0 else 1.0) * buff_mult("stamina_regen") * dt, MAX)
|
||||
var malnourished := diet_lacking().size() >= 2
|
||||
stamina = minf(stamina + STAMINA_REGEN * (1.5 if rested > 0.0 else 1.0) * buff_mult("stamina_regen")
|
||||
* (0.7 if malnourished else 1.0) * dt, MAX)
|
||||
|
||||
for g in DIET_GROUPS:
|
||||
diet[g] = maxf(float(diet[g]) - DIET_DRAIN * dt, 0.0)
|
||||
var lacking := diet_lacking()
|
||||
|
||||
for id in buffs.keys():
|
||||
buffs[id].left -= dt
|
||||
@@ -98,10 +133,13 @@ func step(dt: float) -> void:
|
||||
dmg += 0.6
|
||||
if warmth < 12.0:
|
||||
dmg += 0.5
|
||||
if lacking.size() >= 2:
|
||||
dmg += MALNOURISHED_DRAIN
|
||||
if dmg > 0.0:
|
||||
health = maxf(health - dmg * dt, 0.0)
|
||||
elif hunger > 40.0 and warmth > 35.0 and _hurt_cd <= 0.0:
|
||||
health = minf(health + 0.5 * buff_mult("health_regen") * dt, MAX)
|
||||
elif hunger > 40.0 and warmth > 35.0 and _hurt_cd <= 0.0 and lacking.is_empty():
|
||||
var diet_mult := 1.5 if balanced_diet() else 1.0
|
||||
health = minf(health + 0.5 * buff_mult("health_regen") * diet_mult * dt, MAX)
|
||||
|
||||
_update_statuses()
|
||||
changed.emit()
|
||||
@@ -123,6 +161,14 @@ func _update_statuses() -> void:
|
||||
statuses.append("Hungry")
|
||||
if wet > 0.0:
|
||||
statuses.append("Wet")
|
||||
var lacking := diet_lacking()
|
||||
if not lacking.is_empty():
|
||||
var need: Array[String] = []
|
||||
for g in lacking:
|
||||
need.append(DIET_NAMES[g])
|
||||
statuses.append("%s (need %s)" % ["Malnourished" if lacking.size() >= 2 else "Poor diet", ", ".join(need)])
|
||||
elif balanced_diet():
|
||||
statuses.append("Balanced diet")
|
||||
if sheltered:
|
||||
statuses.append("Sheltered")
|
||||
if rested > 0.0:
|
||||
@@ -145,6 +191,11 @@ func eat(food: Dictionary) -> void:
|
||||
health = clampf(health + float(food.get("health", 0)), minf(health, 1.0), MAX)
|
||||
if food.has("buff"):
|
||||
add_buff(food.buff)
|
||||
var d: Dictionary = food.get("diet", {})
|
||||
for g in d:
|
||||
if diet.has(g):
|
||||
diet[g] = minf(float(diet[g]) + float(d[g]), MAX)
|
||||
_update_statuses()
|
||||
changed.emit()
|
||||
|
||||
func soak() -> void:
|
||||
@@ -168,6 +219,9 @@ func reset_after_death() -> void:
|
||||
warmth = 60.0
|
||||
wet = 0.0
|
||||
buffs.clear()
|
||||
# Don't respawn straight back into malnutrition.
|
||||
for g in DIET_GROUPS:
|
||||
diet[g] = maxf(float(diet[g]), 30.0)
|
||||
changed.emit()
|
||||
|
||||
## Takes a hit (a boar, later wolves). Pauses natural healing briefly.
|
||||
@@ -198,7 +252,7 @@ func add_buff(b: Dictionary) -> void:
|
||||
|
||||
func to_data() -> Dictionary:
|
||||
return {"health": health, "stamina": stamina, "hunger": hunger, "warmth": warmth, "wet": wet, "rested": rested,
|
||||
"buffs": buffs.duplicate(true)}
|
||||
"buffs": buffs.duplicate(true), "diet": diet.duplicate()}
|
||||
|
||||
func load_data(d: Dictionary) -> void:
|
||||
health = float(d.get("health", health))
|
||||
@@ -208,5 +262,9 @@ func load_data(d: Dictionary) -> void:
|
||||
wet = float(d.get("wet", 0.0))
|
||||
rested = float(d.get("rested", 0.0))
|
||||
buffs = (d.get("buffs", {}) as Dictionary).duplicate(true)
|
||||
# Older saves have no diet: start it where a new world does.
|
||||
var sd: Dictionary = d.get("diet", {})
|
||||
for g in DIET_GROUPS:
|
||||
diet[g] = float(sd.get(g, DIET_START))
|
||||
dead = false
|
||||
changed.emit()
|
||||
|
||||
Reference in New Issue
Block a user