Animals stay off the cliffs (Gitea #6): nothing walks up or along ground steeper than 35 degrees, and a carcass on steep ground slides down to where it can be reached and butchered
This commit is contained in:
129
tests/test_cliffs.gd
Normal file
129
tests/test_cliffs.gd
Normal file
@@ -0,0 +1,129 @@
|
||||
extends SceneTree
|
||||
## Gitea #6: animals stay off the cliffs at the edge of the world. A deer
|
||||
## chased outward toward the mountain rim, all the way round, never climbs
|
||||
## steep ground; and one killed on a steep face comes to rest where it's
|
||||
## flatter, so you can reach it and butcher it.
|
||||
## Run: godot --headless --path . --script res://tests/test_cliffs.gd
|
||||
|
||||
var _fails := 0
|
||||
var _finished := false
|
||||
var S: Node
|
||||
var A: Node
|
||||
|
||||
func check(cond: bool, what: String) -> void:
|
||||
print((" ok " if cond else " FAIL ") + what)
|
||||
if not cond:
|
||||
_fails += 1
|
||||
|
||||
func _initialize() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
func _run() -> void:
|
||||
S = root.get_node("Saves")
|
||||
A = root.get_node("Actions")
|
||||
S.root_dir = "user://test_cliffs_worlds"
|
||||
for x in S.list_worlds():
|
||||
S.delete_world(x.id)
|
||||
S.current_id = S.create_world("Valley", 1337)
|
||||
var w: Node3D = load("res://scenes/world.tscn").instantiate()
|
||||
root.add_child(w)
|
||||
for _i in range(8):
|
||||
await physics_frame
|
||||
w.creatures.enabled = false
|
||||
for c in w.creatures.get_children():
|
||||
c.queue_free()
|
||||
if w.wolves != null:
|
||||
w.wolves.enabled = false
|
||||
w.player.set_physics_process(false)
|
||||
w.player.survival.set_process(false)
|
||||
await _body(w)
|
||||
check(_finished, "test ran to the end (no script errors)")
|
||||
for x in S.list_worlds():
|
||||
S.delete_world(x.id)
|
||||
DirAccess.remove_absolute(S.root_dir)
|
||||
print("RESULT: %s (%d failures)" % ["PASS" if _fails == 0 else "FAIL", _fails])
|
||||
quit(1 if _fails > 0 else 0)
|
||||
|
||||
func _body(w: Node3D) -> void:
|
||||
var t: Node3D = w.terrain
|
||||
var p: Node3D = w.player
|
||||
print("chased toward the mountains")
|
||||
# How far into the mountain rim each deer gets (the terrain's own rim
|
||||
# distance), and the steepest ground it walks UP onto.
|
||||
# (Running down a steep bank is allowed: that's how one gets back down.)
|
||||
var steepest := 0.0
|
||||
var highest := 0.0
|
||||
var climb := 0.0
|
||||
var where := ""
|
||||
for i in range(36):
|
||||
var a := TAU * i / 36.0
|
||||
var dir := Vector3(cos(a), 0, sin(a))
|
||||
# (12 m inside the rim, which wobbles in and out by up to 16 m.)
|
||||
var start: Vector3 = dir * (t.play_radius - 12.0 - t._rim_offset(dir.x, dir.z))
|
||||
var d: Node3D = w.creatures.spawn("deer", start)
|
||||
d.set_physics_process(false)
|
||||
for _s in range(600): # (20 s at a run, with you right behind it)
|
||||
p.global_position = d.global_position - dir * 8.0
|
||||
d.player = p
|
||||
d._threat = p.global_position
|
||||
d.state = "flee"
|
||||
d._state_t = 4.0
|
||||
var y0: float = d.global_position.y
|
||||
d.step(1.0 / 30.0)
|
||||
var g: Vector3 = d.global_position
|
||||
if g.y > y0 + 0.0005:
|
||||
var deg := rad_to_deg(acos(clampf(t.sample_normal(g.x, g.z).y, -1.0, 1.0)))
|
||||
if deg > steepest:
|
||||
steepest = deg
|
||||
where = "%d degrees round" % (i * 10)
|
||||
highest = maxf(highest, t._rim_d(g.x, g.z) - t.play_radius)
|
||||
climb = maxf(climb, g.y - start.y - t.sample_height(start.x, start.z) + start.y)
|
||||
d.free()
|
||||
if i % 6 == 0:
|
||||
await process_frame
|
||||
# (Its limit is 35 degrees, checked at a few points ahead, so a few
|
||||
# degrees more can slip through between them; the player's limit is 45:
|
||||
# anywhere a deer goes, you can follow. Before the fix: 71 degrees.)
|
||||
check(steepest < 45.0, "a deer chased outward never walks up ground steeper than you can (steepest %.0f, at %s)" % [steepest, where])
|
||||
# (The rim rises 58 m over 30 m; its first few metres are still gentle.
|
||||
# Before the fix: 8 m in and up onto the cliff face.)
|
||||
check(highest < 6.0 and climb < 6.0, "and stays at the foot of the mountain rim (%.1f m in, %.1f m above where it started)" % [highest, climb])
|
||||
|
||||
print("killed on a cliff")
|
||||
# A steep spot on the rim, inside the play area: where a deer used to end up.
|
||||
var spot := Vector3.INF
|
||||
for i in range(72):
|
||||
var a := TAU * i / 72.0
|
||||
for r in range(int(t.play_radius) - 5, int(t.play_radius) + 15):
|
||||
var x := cos(a) * r
|
||||
var z := sin(a) * r
|
||||
if t.inside_boundary(x, z, 3.0) and t.sample_normal(x, z).y < 0.55:
|
||||
spot = Vector3(x, t.sample_height(x, z), z)
|
||||
break
|
||||
if spot != Vector3.INF:
|
||||
break
|
||||
check(spot != Vector3.INF, "(a 55-degree face on the rim to try it on)")
|
||||
if spot == Vector3.INF:
|
||||
return
|
||||
var deer: Node3D = w.creatures.spawn("deer", spot)
|
||||
deer.set_physics_process(false)
|
||||
deer.take_hit(999.0, spot + Vector3(0, 1, 0))
|
||||
var at: Vector3 = deer.global_position
|
||||
var ny: float = t.sample_normal(at.x, at.z).y
|
||||
check(deer.is_dead() and ny >= deer.CARCASS_REST_NY, "it comes to rest on flatter ground (%.0f degrees)" % rad_to_deg(acos(ny)))
|
||||
check(at.distance_to(spot) < 20.5 and at.y < spot.y, "a little way down the slope (%.1f m)" % at.distance_to(spot))
|
||||
check(absf(at.y - t.sample_height(at.x, at.z)) < 0.05, "lying on the ground, not inside it")
|
||||
p.global_position = at + Vector3(0.8, 0, 0)
|
||||
p.global_position.y = t.sample_height(p.global_position.x, p.global_position.z)
|
||||
var before: int = p.inventory.count("raw_meat")
|
||||
check(A.butcher(p, deer) and p.inventory.count("raw_meat") > before, "and you can butcher it")
|
||||
|
||||
print("on flat ground")
|
||||
var flat: Vector3 = w.build.debug_find_site(w._spawn, 3.0)
|
||||
var d2: Node3D = w.creatures.spawn("deer", flat)
|
||||
d2.set_physics_process(false)
|
||||
var was: Vector3 = d2.global_position
|
||||
d2.take_hit(999.0, was + Vector3(0, 1, 0))
|
||||
check(d2.global_position.distance_to(was) < 0.01, "a deer killed on the flat stays where it fell")
|
||||
d2.free()
|
||||
_finished = true
|
||||
Reference in New Issue
Block a user