161 lines
6.5 KiB
GDScript
161 lines
6.5 KiB
GDScript
extends SceneTree
|
|
## 0.6.0 (docs/WORLDLETS_DESIGN.md §5): the rowing boat. Seagrass washes up
|
|
## at the Cove's tide line and pine trees give resin when felled; seagrass
|
|
## twists into rope, resin and salt boil into pitch at a campfire, and
|
|
## driftwood, rope, pitch and nails make a rowing boat at a workbench. The
|
|
## mooring post at the end of the pier ties it up at a berth of its own, one
|
|
## for each player, and it's saved there. "Set sail" needs the ship's
|
|
## compass, and even then the Highlands aren't charted yet. No pier, no
|
|
## moorings: none in the Valley.
|
|
## Run: godot --headless --path . --script res://tests/test_boat.gd
|
|
|
|
var _fails := 0
|
|
var _finished := false
|
|
var S: Node
|
|
var A: Node
|
|
var DB: Node
|
|
var _toasts: Array[String] = []
|
|
|
|
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")
|
|
DB = root.get_node("ItemDB")
|
|
A.notify.connect(func(t): _toasts.append(t))
|
|
S.root_dir = "user://test_boat_worlds"
|
|
for x in S.list_worlds():
|
|
S.delete_world(x.id)
|
|
await _body()
|
|
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 _load() -> Node3D:
|
|
var x: Node3D = load("res://scenes/world.tscn").instantiate()
|
|
root.add_child(x)
|
|
for _i in range(8):
|
|
await physics_frame
|
|
x.creatures.enabled = false
|
|
x.player.set_physics_process(false)
|
|
x.player.survival.set_process(false)
|
|
return x
|
|
|
|
func _drop(x: Node3D) -> void:
|
|
x.queue_free()
|
|
for _i in range(3):
|
|
await process_frame
|
|
|
|
func _toast_has(s: String) -> bool:
|
|
return _toasts.any(func(t): return String(t).contains(s))
|
|
|
|
func _body() -> void:
|
|
print("the Valley")
|
|
S.current_id = S.create_world("Valley", 1337)
|
|
var w: Node3D = await _load()
|
|
check(w.moorings == null, "no pier, no moorings")
|
|
var resin: Array = w.props.resource_nodes.values().filter(func(n): return n.drops_final.has("pine_resin"))
|
|
check(resin.size() > 20, "its pines give resin when felled (%d trees)" % resin.size())
|
|
await _drop(w)
|
|
|
|
print("the Cove")
|
|
var cove: String = S.create_world("Cove", 1337, {"worldlet": "cove"})
|
|
S.current_id = cove
|
|
w = await _load()
|
|
var t: Node3D = w.terrain
|
|
var mo: Node3D = w.moorings
|
|
check(mo != null, "moorings at the pier")
|
|
if mo == null:
|
|
return
|
|
check(mo.post.global_position.distance_to(w.pier.end) < 2.0, "a mooring post at the end of the pier")
|
|
var deep := true
|
|
var depths: Array = []
|
|
for i in range(mo.BERTHS):
|
|
var b: Transform3D = mo.berth_xf(i)
|
|
var wa: Dictionary = w.water_at(b.origin.x, b.origin.z)
|
|
depths.append("%.1f" % float(wa.get("depth", -1.0)))
|
|
deep = deep and not wa.is_empty() and float(wa.depth) > 0.6 and not w.pier.covers(b.origin.x, b.origin.z, 0.2)
|
|
check(deep, "%d berths alongside it, all in open water (%s m deep)" % [mo.BERTHS, ", ".join(depths)])
|
|
var grass: Array = w.props.pickups.values().filter(func(p): return p.item_id == "seagrass")
|
|
check(grass.size() >= 15 and grass.all(func(p): return t.on_sand(p.global_position.x, p.global_position.z)),
|
|
"seagrass at the tide line (%d)" % grass.size())
|
|
|
|
print("building it")
|
|
var p: Node3D = w.player
|
|
p.global_position = w.build.debug_find_site(w._spawn, 3.0)
|
|
var inv = p.inventory
|
|
inv.add("seagrass", 12)
|
|
inv.add("pine_resin", 6)
|
|
inv.add("salt", 3)
|
|
inv.add("driftwood", 12)
|
|
inv.add("nails", 6)
|
|
var ok := true
|
|
for i in range(4):
|
|
ok = A.craft(p, "rope") and ok
|
|
check(ok and inv.count("rope") == 4 and inv.count("seagrass") == 0, "3 seagrass twist into a rope, by hand")
|
|
check(not A.craft(p, "pitch"), "pitch needs a fire")
|
|
var fire: Node3D = w.spawn_piece(DB.get_item("campfire"), Transform3D(Basis(), p.global_position + Vector3(2, 0, 0)))
|
|
for i in range(3):
|
|
ok = A.craft(p, "pitch") and ok
|
|
check(ok and inv.count("pitch") == 3 and inv.count("salt") == 0, "2 resin and a salt boil into pitch at a campfire")
|
|
check(not A.craft(p, "rowing_boat"), "the boat needs a workbench")
|
|
w.spawn_piece(DB.get_item("workbench"), Transform3D(Basis(), p.global_position + Vector3(-2, 0, 0)))
|
|
check(A.craft(p, "rowing_boat") and inv.count("rowing_boat") == 1, "driftwood, rope, pitch and nails: a rowing boat")
|
|
check(inv.count("driftwood") == 0 and inv.count("rope") == 0 and inv.count("pitch") == 0 and inv.count("nails") == 0, "(all used)")
|
|
w.remove_piece(fire)
|
|
|
|
print("tying it up")
|
|
p.global_position = mo.post.global_position + w.pier.dir * -1.0
|
|
var me: String = mo.post.id_of(p)
|
|
check(mo.post.interact_prompt(p).contains("[E] Tie up your boat"), "the post: [E] Tie up your boat")
|
|
_toasts.clear()
|
|
mo.post.interact(p)
|
|
check(mo.has_boat(me) and inv.count("rowing_boat") == 0, "it's tied up, out of your bag")
|
|
check(_toast_has("tie your boat up"), "(and it says so)")
|
|
var boat: Node3D = mo.boats.get(me)
|
|
check(boat != null and boat.global_position.distance_to(mo.berth_xf(int(mo.moored[me].berth)).origin) < 0.3, "at a berth by the pier")
|
|
check(boat != null and boat.interact_prompt(p) == "Your rowing boat [E] Set sail", "\"Your rowing boat [E] Set sail\"")
|
|
check(mo.post.interact_prompt(p).contains("your boat's tied up"), "the post knows")
|
|
inv.add("rowing_boat", 1)
|
|
_toasts.clear()
|
|
check(not mo.moor(p, me) and inv.count("rowing_boat") == 1, "one boat each: a second doesn't go in")
|
|
inv.remove("rowing_boat", 1)
|
|
|
|
print("setting sail")
|
|
_toasts.clear()
|
|
boat.interact(p)
|
|
check(_toast_has("without a ship's compass"), "without a ship's compass you'd be lost (%s)" % [_toasts])
|
|
inv.add("ship_compass", 1)
|
|
_toasts.clear()
|
|
boat.interact(p)
|
|
check(_toast_has("nobody's charted the way to the Highlands"), "with it: the Highlands aren't charted yet")
|
|
check(mo.has_boat(me), "(and it stays tied up)")
|
|
|
|
print("someone else's")
|
|
inv.add("rowing_boat", 1)
|
|
check(mo.moor(p, "someone-else"), "someone else ties up theirs")
|
|
check(mo.boats.size() == 2 and int(mo.moored["someone-else"].berth) != int(mo.moored[me].berth), "at a berth of its own")
|
|
check(mo.boats["someone-else"].interact_prompt(p) == "Someone's rowing boat", "not yours: \"Someone's rowing boat\"")
|
|
_toasts.clear()
|
|
mo.boats["someone-else"].interact(p)
|
|
check(_toasts.is_empty(), "and you can't sail it")
|
|
|
|
print("saved")
|
|
var state: Dictionary = w.collect_save()
|
|
S.save_state(cove, state, 1.0)
|
|
await _drop(w)
|
|
w = await _load()
|
|
mo = w.moorings
|
|
check(mo != null and mo.has_boat(me) and mo.has_boat("someone-else") and mo.boats.size() == 2, "both boats are still there after a reload")
|
|
await _drop(w)
|
|
_finished = true
|