W2: several lakes per worldlet, a beach block shelving into the sea with a current that turns swimmers back; testbay test worldlet through the water, edge and boundary suites; the two ways to play (journey, one land) recorded
This commit is contained in:
123
tests/test_water_bodies.gd
Normal file
123
tests/test_water_bodies.gd
Normal file
@@ -0,0 +1,123 @@
|
||||
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
|
||||
Reference in New Issue
Block a user