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:
@@ -759,12 +759,17 @@ func _make_grass_tile(k: Vector2i) -> Node3D:
|
||||
## half width]. Set by world.gd whenever floors are built or removed; only the
|
||||
## grass tiles around old and new floors are rebuilt.
|
||||
var _grass_cuts: Array = []
|
||||
## Permanent cuts (the wolf den floor): [transform, half width x, half depth z].
|
||||
var static_cuts: Array = []
|
||||
|
||||
func set_grass_cuts(xforms: Array, half: float) -> void:
|
||||
var old := _grass_cuts
|
||||
_grass_cuts = []
|
||||
for xf: Transform3D in xforms:
|
||||
_grass_cuts.append([xf.affine_inverse(), xf.origin, half])
|
||||
_grass_cuts.append([xf.affine_inverse(), xf.origin, half, half])
|
||||
for c in static_cuts:
|
||||
var xf: Transform3D = c[0]
|
||||
_grass_cuts.append([xf.affine_inverse(), xf.origin, float(c[1]), float(c[2])])
|
||||
var touched := {}
|
||||
for c in old + _grass_cuts:
|
||||
var p: Vector3 = c[1]
|
||||
@@ -783,7 +788,7 @@ func set_grass_cuts(xforms: Array, half: float) -> void:
|
||||
static func _under_cut(cuts: Array, x: float, z: float) -> bool:
|
||||
for c in cuts:
|
||||
var l: Vector3 = (c[0] as Transform3D) * Vector3(x, (c[1] as Vector3).y, z)
|
||||
if absf(l.x) < c[2] and absf(l.z) < c[2]:
|
||||
if absf(l.x) < c[2] and absf(l.z) < c[3]:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ var weather_fx: Node3D ## weather_fx.gd: rain/snow particles, sound
|
||||
var build: Node ## build_system.gd: snapping, support, shelter
|
||||
var creatures: Node3D ## spawner.gd: wildlife around the player
|
||||
var _dropped: Node3D ## arrows, spears and other dropped items
|
||||
var den: Node3D ## den.gd: the wolves' cave at the foot of the rim
|
||||
var bobber: Node3D ## bobber.gd: the cast fishing float, if any
|
||||
var wolves: Node3D ## wolf_pack.gd (null if this seed has no den site)
|
||||
var has_bed_spawn := false
|
||||
var bed_spawn := Vector3.ZERO ## position of the bed you respawn at
|
||||
var _loading := false ## applying a save: defer support recompute
|
||||
@@ -193,6 +196,10 @@ func sleep_block_reason(bed: Node3D) -> String:
|
||||
return "You can only sleep at night (after 19:30)"
|
||||
if not build.sheltered_at(bed.global_position):
|
||||
return "Your bed needs a roof and walls around it"
|
||||
if wolves != null:
|
||||
for w in wolves.alive():
|
||||
if w.state in ["stalk", "lunge", "defend", "retreat"] and w.global_position.distance_to(bed.global_position) < 40.0:
|
||||
return "You can't sleep with wolves prowling outside"
|
||||
return ""
|
||||
|
||||
## Sleeps until 06:00: the clock, fires, regrowth and weather all move on.
|
||||
@@ -227,6 +234,18 @@ func _on_terrain_ready() -> void:
|
||||
_spawn_yaw = atan2(-to_lake.x, -to_lake.y)
|
||||
player.water_level = terrain.water_level
|
||||
props.place_on(terrain, player if not menu_mode else _menu_cam, _spawn)
|
||||
# The wolf den: part of the scenery, so it's there from day 1 (empty).
|
||||
den = preload("res://scripts/creatures/den.gd").new()
|
||||
den.name = "WolfDen"
|
||||
add_child(den)
|
||||
if den.place(self, props._rock_mat):
|
||||
props.static_cuts.append([den.global_transform, 2.2, 3.4])
|
||||
_update_grass_cuts()
|
||||
if DisplayServer.get_name() != "headless":
|
||||
var fish := preload("res://scripts/fishing/lake_fish.gd").new()
|
||||
fish.name = "LakeFish"
|
||||
add_child(fish)
|
||||
fish.setup(terrain)
|
||||
print("[world] scenery placed in %d ms" % (Time.get_ticks_msec() - t0))
|
||||
if menu_mode:
|
||||
return
|
||||
@@ -242,6 +261,11 @@ func _on_terrain_ready() -> void:
|
||||
if not "--no-creatures" in OS.get_cmdline_user_args():
|
||||
creatures.populate()
|
||||
creatures.enabled = true
|
||||
if den.found:
|
||||
wolves = preload("res://scripts/creatures/wolf_pack.gd").new()
|
||||
wolves.name = "Wolves"
|
||||
add_child(wolves)
|
||||
wolves.setup(self, den)
|
||||
_maybe_capture()
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
@@ -474,6 +498,19 @@ func drop_item(id: String, count: int, xf: Transform3D) -> Node3D:
|
||||
pk.global_transform = xf
|
||||
return pk
|
||||
|
||||
## Casts a fishing float (Actions validated it). One at a time.
|
||||
func start_fishing(p: Node3D, origin: Vector3, vel: Vector3) -> Node3D:
|
||||
end_fishing()
|
||||
bobber = preload("res://scripts/fishing/bobber.gd").new()
|
||||
bobber.setup(self, p, origin, vel, randi())
|
||||
add_child(bobber)
|
||||
return bobber
|
||||
|
||||
func end_fishing() -> void:
|
||||
if bobber != null and is_instance_valid(bobber):
|
||||
bobber.queue_free()
|
||||
bobber = null
|
||||
|
||||
## Fires an arrow or thrown spear (Actions validated it).
|
||||
func launch(id: String, origin: Vector3, vel: Vector3, damage: float, shooter: Node3D) -> Node3D:
|
||||
var pr: Node3D = preload("res://scripts/creatures/projectile.gd").new()
|
||||
@@ -643,6 +680,103 @@ func _capture_survival(prefix: String, cam: Camera3D) -> void:
|
||||
hud._menu.close()
|
||||
await _capture_weather(prefix, cam)
|
||||
await _capture_creatures(prefix, cam)
|
||||
await _capture_wolves_fishing(prefix, cam)
|
||||
|
||||
## The den by day with the pack resting, wolves stalking at night, and
|
||||
## fishing from the lake shore.
|
||||
func _capture_wolves_fishing(prefix: String, cam: Camera3D) -> void:
|
||||
if wolves == null:
|
||||
return
|
||||
for c in creatures.get_children():
|
||||
c.queue_free()
|
||||
wolves.enabled = false
|
||||
day_night.day = 5
|
||||
day_night.time = 10.0 / 24.0
|
||||
day_night.apply(true)
|
||||
wolves.step(0.1)
|
||||
var out: Vector3 = (den.mouth_pos() - den.position).normalized()
|
||||
var eye: Vector3 = den.position + Basis(Vector3.UP, 0.35) * out * 11.0
|
||||
eye.y = terrain.sample_height(eye.x, eye.z) + 1.7
|
||||
player.select(0)
|
||||
_aim_camera(cam, eye, den.position + Vector3(0, 1.0, 0))
|
||||
for w in wolves.alive():
|
||||
w.set_physics_process(false)
|
||||
for _i in range(30):
|
||||
w._animate(0.1)
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
await RenderingServer.frame_post_draw
|
||||
get_viewport().get_texture().get_image().save_png("%s_den.png" % prefix)
|
||||
print("[world] saved %s_den.png" % prefix)
|
||||
|
||||
# Night: the pack circling you in the open.
|
||||
var site: Vector3 = build.debug_find_site(_spawn + Vector3(-10, 0, 10), 6.0)
|
||||
site.y = terrain.sample_height(site.x, site.z)
|
||||
day_night.time = 22.5 / 24.0
|
||||
day_night.apply(true)
|
||||
var live: Array = wolves.alive()
|
||||
for i in range(live.size()):
|
||||
var a := -0.5 + i * 0.7
|
||||
var p: Vector3 = site + Vector3(sin(a), 0, -cos(a)) * (7.0 + i * 1.5)
|
||||
live[i].global_position = Vector3(p.x, terrain.sample_height(p.x, p.z), p.z)
|
||||
live[i].state = "stalk"
|
||||
var to: Vector3 = site - p
|
||||
live[i].yaw = atan2(-to.x, -to.z)
|
||||
live[i].rotation.y = live[i].yaw
|
||||
live[i]._visual.position.y = 0.0
|
||||
for l in live[i]._legs:
|
||||
l.rotation.x = 0.0
|
||||
player.inventory.set_slot(1, {"id": "torch", "count": 1})
|
||||
player.select(1)
|
||||
var e2: Vector3 = site + Vector3(0, 1.7, 0)
|
||||
_aim_camera(cam, e2, site + Vector3(0, 0.6, -8.0))
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
await RenderingServer.frame_post_draw
|
||||
get_viewport().get_texture().get_image().save_png("%s_wolves_night.png" % prefix)
|
||||
print("[world] saved %s_wolves_night.png" % prefix)
|
||||
for w in live:
|
||||
w.global_position = den.slot_pos(w.slot)
|
||||
|
||||
# Fishing at the lake in the evening light, a fish on the line.
|
||||
day_night.time = 18.2 / 24.0
|
||||
day_night.apply(true)
|
||||
var lake: Vector2 = terrain.lake_center
|
||||
var x: float = lake.x
|
||||
while terrain.sample_height(x, lake.y) < terrain.water_level + 0.4 and x < lake.x + 60.0:
|
||||
x += 0.5
|
||||
var shore := Vector3(x + 1.0, 0, lake.y)
|
||||
shore.y = terrain.sample_height(shore.x, shore.z)
|
||||
player.inventory.set_slot(2, {"id": "fishing_rod", "count": 1})
|
||||
player.select(2)
|
||||
var fe: Vector3 = shore + Vector3(0, 1.7, 0)
|
||||
# Midday, looking at the nearest fish, one mid-jump.
|
||||
var lf: Node = get_node_or_null("LakeFish")
|
||||
if lf != null and not lf._fish.is_empty():
|
||||
day_night.time = 12.5 / 24.0
|
||||
day_night.apply(true)
|
||||
var t_now := Time.get_ticks_msec() / 1000.0
|
||||
var best := 0
|
||||
for i in range(lf._fish.size()):
|
||||
if lf._swim_pos(i, t_now).distance_to(fe) < lf._swim_pos(best, t_now).distance_to(fe):
|
||||
best = i
|
||||
_aim_camera(cam, fe, lf._swim_pos(best, t_now))
|
||||
lf._jump_left = 0.4
|
||||
await get_tree().create_timer(0.75).timeout
|
||||
await RenderingServer.frame_post_draw
|
||||
get_viewport().get_texture().get_image().save_png("%s_lake_fish.png" % prefix)
|
||||
print("[world] saved %s_lake_fish.png (fish %.0f m away)" % [prefix, lf._swim_pos(best, t_now).distance_to(fe)])
|
||||
day_night.time = 18.2 / 24.0
|
||||
day_night.apply(true)
|
||||
_aim_camera(cam, fe, Vector3(lake.x + 6.0, terrain.water_level - 1.5, lake.y + 2.0))
|
||||
await get_tree().create_timer(0.3).timeout
|
||||
start_fishing(player, player.hands.rod_tip(), Vector3.ZERO)
|
||||
bobber.set_physics_process(false)
|
||||
bobber.global_position = Vector3(lake.x + 9.0, terrain.water_level, lake.y + 1.5)
|
||||
bobber.state = "biting"
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
await RenderingServer.frame_post_draw
|
||||
get_viewport().get_texture().get_image().save_png("%s_fishing.png" % prefix)
|
||||
print("[world] saved %s_fishing.png" % prefix)
|
||||
end_fishing()
|
||||
|
||||
## Wildlife views: a rabbit, a deer and a boar in a meadow, the bow drawn on
|
||||
## a deer, and a carcass with an arrow in it.
|
||||
|
||||
Reference in New Issue
Block a user