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