extends SceneTree ## W3 (docs/WORLDLETS_DESIGN.md ยง5): the Cove. A world made there has its ## bay, a pond and a pier; you walk from the sand onto the pier, cast off its ## end into the sea and catch a mackerel; cod only bite in deeper water; the ## sea fish cook like the lake's. The Cove isn't a start (you reach it on ## the journey), and its ways on are back to the Valley and on by sail. ## Run: godot --headless --path . --script res://tests/test_cove.gd var _fails := 0 var _finished := false var A: Node var S: Node var DB: 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: A = root.get_node("Actions") S = root.get_node("Saves") DB = root.get_node("ItemDB") S.root_dir = "user://test_cove_worlds" for w in S.list_worlds(): S.delete_world(w.id) await _body() 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() -> void: var T: Script = load("res://scripts/world/terrain.gd") print("the Cove") var def: Dictionary = T.load_worldlet("cove") check(def.get("name", "") == "Cove" and not bool(def.get("start", true)), "a worldlet, not a start (reached on the journey)") S.current_id = S.create_world("Cove", 1337, {"worldlet": "cove"}) 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 var player: CharacterBody3D = world.player world.day_night.frozen = true world.weather.force("clear") world.creatures.enabled = false world.day_night.time = 0.5 world.day_night.apply(true) player.survival.set_process(false) check(world.worldlet_id() == "cove", "a world made in the Cove is the Cove") var bodies: Array = world.water_bodies().map(func(b): return b.type) check(bodies.has("sea") and bodies.has("lake"), "with the sea and a pond (%s)" % [bodies]) check(world.crossings().any(func(c): return c.to == "valley") and world.crossings().any(func(c): return c.by == "sail"), "its ways on: back to the Valley, and on by sail") check(world._spawn.y > t.water_level + 1.0, "you start on dry land") check(world.den != null and world.barrow != null and world.stones != null, "the den, the barrow and the stones found places") check(t.get_node_or_null("Water") == null, "one water surface (the bay is the sea)") print("the stream") check(t.stream_path.size() >= 2 and t.get_node_or_null("Stream") != null, "a stream, with water in it") var downhill := true for i in range(1, t._stream_profile.size()): downhill = downhill and t._stream_profile[i] <= t._stream_profile[i - 1] + 0.0001 check(downhill and t._stream_profile[0] > t.water_level + 1.5, "running downhill all the way from its spring (%.1f m) to the sea" % t._stream_profile[0]) var mid: Vector2 = t._stream_point(t._stream_end * 0.3) var sw: Dictionary = world.water_at(mid.x, mid.y) check(sw.get("type", "") == "stream" and float(sw.depth) > 0.3 and float(sw.depth) < 1.0, "stream water, wading deep (%.1f m)" % float(sw.get("depth", 0.0))) var side: Vector2 = mid + Vector2(t.stream_width * 1.2, 0.0) check(world.water_at(side.x, side.y).is_empty() and t.sample_height(side.x, side.y) > float(sw.surface), "its banks are dry, and above the water") check(DB.water_fishing("stream").get("fish", []).any(func(f): return f.id == "trout"), "trout in the stream") var away := true for p in [world.den.global_position, world.barrow.global_position, world.stones.global_position, world._spawn]: away = away and not t.near_stream(p.x, p.z, 3.0) check(away, "the den, the barrow, the stones and the start are all clear of it") var in_bed := 0 for n in world.props.resource_nodes.values(): if is_instance_valid(n) and t.near_stream(n.global_position.x, n.global_position.z, 0.0): in_bed += 1 check(in_bed == 0, "no trees or boulders standing in it") print("the pier") var pier: Node3D = world.pier check(pier != null and pier.is_in_group("pier"), "a pier") if pier == null: return var len: float = pier.start.distance_to(pier.end) check(len > 20.0, "running out over the bay (%.0f m)" % len) var at_end: Dictionary = world.water_at(pier.end.x, pier.end.z) check(at_end.get("type", "") == "sea" and float(at_end.depth) > 1.0, "into the sea (%.1f m deep at the end)" % float(at_end.get("depth", 0.0))) check(absf(t.sample_height(pier.start.x, pier.start.z) - pier.start.y) < 0.3, "starting at the sand, so you walk straight on") # Walk on from the sand: the deck holds you up above the water. var on: Vector3 = pier.start - pier.dir * 1.5 on.y = t.sample_height(on.x, on.z) + 0.2 player.global_position = on player.velocity = Vector3.ZERO player.rotation = Vector3(0.0, atan2(-pier.dir.x, -pier.dir.z), 0.0) Input.action_press("move_forward") for _i in range(int(4.0 * Engine.physics_ticks_per_second)): await physics_frame Input.action_release("move_forward") var p: Vector3 = player.global_position var along: float = (p - pier.start).dot(pier.dir) check(along > 10.0 and p.y > t.water_level + 0.5, "walk on from the beach and along it, dry (%.0f m out, %.1f m above the water)" % [along, p.y - t.water_level]) check(not world._in_current, "(no current up on the deck)") print("sea fishing") player.set_physics_process(false) player.global_position = pier.end - pier.dir * 0.8 player.inventory.add("fishing_rod", 1) _hold(player, "fishing_rod") var out: Vector3 = pier.end + pier.dir * 8.0 out.y = t.water_level var got := {} var bobs := 0 for _try in range(25): player.survival.stamina = 100.0 if not _cast(world, player, out): break var b := _bob_until(world, "floating", 5.0) if b == null or b.state != "floating": break bobs += 1 if bobs == 1: check(b.water_type == "sea", "the float lands on the sea (%.1f m deep)" % b.water_depth) b = _bob_until(world, "biting", 60.0) if b == null or b.state != "biting": break var fish: String = b.fish var before: int = player.inventory.count(fish) if A.reel(player, world) and player.inventory.count(fish) == before + 1: got[fish] = int(got.get(fish, 0)) + 1 if got.has("mackerel") and _try >= 6: break check(got.has("mackerel"), "you catch a mackerel off the pier (%s)" % [got]) check(got.keys().all(func(k): return k in ["mackerel", "cod"]), "(sea fish only: no perch in the sea)") print("cod like it deep") var bob: Node3D = load("res://scripts/fishing/bobber.gd").new() bob.table = DB.water_fishing("sea") bob.world = world bob.rng.seed = 5 var shallow := {} var deep := {} for i in range(200): bob.water_depth = 1.5 shallow[bob._pick_fish()] = true bob.water_depth = 3.0 deep[bob._pick_fish()] = true check(not shallow.has("cod") and shallow.has("mackerel"), "in shallow sea only mackerel bite (%s)" % [shallow.keys()]) check(deep.has("cod") and deep.has("mackerel"), "cast out deep and cod bite too") bob.free() print("cooking them") for fish in ["mackerel", "cod"]: var r: Dictionary = DB.recipes.get("grilled_" + fish, {}) check(r.get("station", "") == "campfire" and r.get("inputs", {}).has(fish) and not DB.get_item("grilled_" + fish).is_empty(), "grilled %s, at a campfire" % fish) check(DB.recipes.get("salted_" + fish, {}).get("output", "") == "salted_fish", "and %s salts into salted fish" % fish) check(not DB.get_item("mackerel").get("food", {}).is_empty() and DB.get_item("cod").get("food", {}).get("diet", {}).has("fish"), "the raw fish count as fish in your diet") world.queue_free() await process_frame _finished = true func _cast(world: Node3D, player: Node3D, toward: Vector3) -> bool: var origin: Vector3 = player.global_position + Vector3(0, 1.6, 0) return A.cast_line(player, origin, toward - origin, world) ## Steps the float until it reaches `want` (or `secs` pass). Returns it. func _bob_until(world: Node3D, want: String, secs: float) -> Node3D: var tt := 0.0 while tt < secs: var b: Node3D = world.bobber if b == null or not is_instance_valid(b): return null if b.state == want: return b b.set_physics_process(false) b.step(0.05) tt += 0.05 return world.bobber func _hold(player: Node, id: String) -> void: var inv: Inventory = player.inventory for i in range(inv.size()): var s = inv.get_slot(i) if s != null and s.id == id: var tmp = inv.get_slot(0) inv.set_slot(0, s) inv.set_slot(i, tmp) break player.select(0)