Files
Cinderwild/tests/test_water_bodies.gd

124 lines
5.2 KiB
GDScript

extends SceneTree
## W2 (docs/WORLDLETS_DESIGN.md): several bodies of water in one world, and a
## beach. On a test worldlet with two ponds and a beach
## (tests/worldlets/testbay.json): the world lists both ponds and the sea,
## water_at() knows which is which, the beach shelves gently into the sea,
## the sea there is one surface (no second water sheet), and a swimmer out
## past the shallows is carried back by the current. The Valley has no
## current anywhere.
## Run: godot --headless --path . --script res://tests/test_water_bodies.gd
var _fails := 0
var _finished := false
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:
var S: Node = root.get_node("Saves")
S.root_dir = "user://test_water_worlds"
for w in S.list_worlds():
S.delete_world(w.id)
await _body(S)
check(_finished, "test ran to the end (no script errors)")
for w in S.list_worlds():
S.delete_world(w.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(S: Node) -> void:
var T: Script = load("res://scripts/world/terrain.gd")
check(not T.worldlets().any(func(w): return w.id == "testbay"), "the test worldlet is never listed (nor offered)")
print("the Valley: no current")
var v: Node3D = T.new()
root.add_child(v)
await process_frame
var any := false
for i in range(72):
var a := TAU * i / 72.0
for r in [60.0, 100.0, 110.0, 120.0]:
any = any or v.current_at(cos(a) * r, sin(a) * r) > 0.0
check(not any, "nowhere in the Valley has a current")
check(v.lakes.size() == 1 and v.lakes[0].center == Vector2(14, -46), "and its one lake is the old one")
v.free()
print("two ponds and a beach")
S.current_id = S.create_world("Bay", 1337, {"worldlet": "testbay"})
var world: Node3D = load("res://scenes/world.tscn").instantiate()
root.add_child(world)
for _i in range(8):
await physics_frame
var t: Node3D = world.terrain
check(world.worldlet_id() == "testbay", "the world is the test bay")
var bodies: Array = world.water_bodies()
var ponds: Array = bodies.filter(func(b): return b.type == "lake")
var seas: Array = bodies.filter(func(b): return b.type == "sea")
check(ponds.size() == 2 and seas.size() == 1, "its water: two ponds and the sea (%s)" % [bodies.map(func(b): return b.type)])
for p in ponds:
var c: Vector2 = p.center
var w: Dictionary = world.water_at(c.x, c.y)
check(w.get("type", "") == "lake" and float(w.get("depth", 0.0)) > 2.0,
"the pond at (%.0f, %.0f) is water, %.1f m deep" % [c.x, c.y, float(w.get("depth", 0.0))])
var bay: Vector2 = seas[0].center
var sw: Dictionary = world.water_at(bay.x, bay.y)
check(sw.get("type", "") == "sea", "out in the bay is the sea (%s)" % [sw])
check(absf(t.sea_level - t.water_level) < 0.01, "the sea off a beach is at the ponds' level (%.1f)" % t.sea_level)
check(t.get_node_or_null("Water") == null and t.get_node_or_null("Ocean") != null, "one water surface: the ocean, no second sheet")
print("the beach")
# Walking straight out from the middle of the beach: dry land, the
# waterline, then gently deeper (no cliff), then the current, then the wall.
var dir := Vector2(cos(deg_to_rad(t.beach_angle_deg)), sin(deg_to_rad(t.beach_angle_deg)))
var shore := -1.0
var current := -1.0
var wall := -1.0
var steepest := 0.0
var prev: float = t.sample_height(dir.x * 40.0, dir.y * 40.0)
var r := 41.0
while r < 150.0:
var q := dir * r
var h: float = t.sample_height(q.x, q.y)
if r > 60.0:
steepest = maxf(steepest, absf(h - prev))
prev = h
if shore < 0.0 and h < t.water_level:
shore = r
if current < 0.0 and t.current_at(q.x, q.y) > 0.0:
current = r
if wall < 0.0 and not t.inside_boundary(q.x, q.y):
wall = r
r += 1.0
check(shore > 0.0 and current > shore and wall > current, "dry land, the waterline at %.0f m, the current from %.0f m, the wall at %.0f m" % [shore, current, wall])
check(steepest < 0.5, "a gentle shelf, never a drop (steepest %.2f m per metre)" % steepest)
var depth_at_current: float = t.water_level - t.sample_height(dir.x * current, dir.y * current)
check(depth_at_current > 1.3, "the current starts where you're swimming, not wading (%.1f m deep)" % depth_at_current)
print("the current")
var player: CharacterBody3D = world.player
player.set_physics_process(false) # (no swimming: just drift)
var start := dir * (wall - 4.0)
player.global_position = Vector3(start.x, t.water_level - 1.3, start.y)
var d0 := start.length()
for _i in range(60):
await physics_frame
var p := player.global_position
var d1 := Vector2(p.x, p.z).length()
check(d1 < d0 - 1.5, "a swimmer by the wall is carried back towards the shore (%.1f m in a second)" % (d0 - d1))
check(world._in_current, "(and told so)")
var near := dir * (shore + 2.0)
player.global_position = Vector3(near.x, t.water_level - 0.5, near.y)
for _i in range(30):
await physics_frame
p = player.global_position
check(absf(Vector2(p.x, p.z).length() - (shore + 2.0)) < 0.01, "but nothing pulls at you in the shallows")
check(t.find_spawn(Vector2.ZERO).y > t.water_level + 1.0, "the start is on dry land")
world.queue_free()
await process_frame
_finished = true