184 lines
7.8 KiB
GDScript
184 lines
7.8 KiB
GDScript
extends SceneTree
|
|
## 0.6.0 (docs/WORLDLETS_DESIGN.md §5): the Cove's wreck and the treasure map.
|
|
## The wreck is on the Cove's beach (and not in the Valley); driftwood and
|
|
## salt wash up on the sand; the captain's sea chest gives each player a
|
|
## treasure map, nails and salt, once; the map's X is that player's own (and
|
|
## somewhere else for the next player); reading the map tells you, and the X
|
|
## is on your N map while you carry it; who's had their share is saved.
|
|
## Run: godot --headless --path . --script res://tests/test_wreck.gd
|
|
|
|
var _fails := 0
|
|
var _finished := false
|
|
var S: Node
|
|
var A: 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")
|
|
A.notify.connect(func(t): _toasts.append(t))
|
|
S.root_dir = "user://test_wreck_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 _load() -> Node3D:
|
|
var w: Node3D = load("res://scenes/world.tscn").instantiate()
|
|
root.add_child(w)
|
|
for _i in range(8):
|
|
await physics_frame
|
|
w.creatures.enabled = false
|
|
w.player.set_physics_process(false)
|
|
w.player.survival.set_process(false)
|
|
return w
|
|
|
|
func _drop(w: Node3D) -> void:
|
|
w.queue_free()
|
|
for _i in range(3):
|
|
await process_frame
|
|
|
|
func _body() -> void:
|
|
print("the Valley")
|
|
S.current_id = S.create_world("Valley", 1337)
|
|
var w: Node3D = await _load()
|
|
check(w.wreck == null, "no wreck in the Valley")
|
|
check(not w.props.pickups.values().any(func(p): return p.item_id == "driftwood"), "and no driftwood")
|
|
await _drop(w)
|
|
|
|
print("the wreck")
|
|
S.current_id = S.create_world("Cove", 1337, {"worldlet": "cove"})
|
|
w = await _load()
|
|
var t: Node3D = w.terrain
|
|
var wk: Node3D = w.wreck
|
|
check(wk != null and t.beach_weight(wk.global_position.x, wk.global_position.z) > 0.5, "a wreck on the Cove's beach")
|
|
if wk == null:
|
|
return
|
|
check(wk.global_position.distance_to(w.sea_cave.global_position) > 60.0, "at the far end of the bay from the sea cave (%.0f m)" % wk.global_position.distance_to(w.sea_cave.global_position))
|
|
check(w.water_at(wk.front.x, wk.front.z).is_empty() or float(w.water_at(wk.front.x, wk.front.z).depth) < 0.6, "you can stand beside it to reach the chest")
|
|
var drift: Array = w.props.pickups.values().filter(func(p): return p.item_id == "driftwood")
|
|
check(drift.size() >= 20 and drift.all(func(p): return t.beach_weight(p.global_position.x, p.global_position.z) > 0.5),
|
|
"driftwood along the sand (%d)" % drift.size())
|
|
check(w.props.pickups.values().any(func(p): return p.item_id == "salt"), "and salt crusts by the waterline")
|
|
|
|
print("the captain's sea chest")
|
|
var p: Node3D = w.player
|
|
var me: String = w.my_id()
|
|
p.global_position = wk.front
|
|
check(wk.chest.interact_prompt(p).begins_with("[E] Open"), "[E] Open the captain's sea chest")
|
|
wk.chest.interact(p)
|
|
check(p.inventory.count("treasure_map") == 1 and p.inventory.count("nails") >= 4 and p.inventory.count("salt") >= 2,
|
|
"a torn treasure map, nails and salt")
|
|
check(_toasts.any(func(s): return String(s).begins_with("In the captain's sea chest")), "(and it says so)")
|
|
_toasts.clear()
|
|
wk.chest.interact(p)
|
|
check(p.inventory.count("treasure_map") == 1 and _toasts.has("The sea chest's empty: you've had what was in it"), "once: the second time it's empty")
|
|
check(wk.open_for(p, "someone-else"), "someone else gets their own share")
|
|
p.inventory.remove("treasure_map", 1) # (it went in this bag: theirs, not ours)
|
|
|
|
print("the map")
|
|
var x: Vector3 = w.treasure_spot(me)
|
|
check(x != Vector3.INF and t.inside_boundary(x.x, x.z, 10.0) and x.y > t.water_level + 1.5, "your X: dry ground in the hills (%s)" % x)
|
|
var others := 0
|
|
for i in range(8):
|
|
if w.treasure_spot("player-%d" % i).distance_to(x) > 20.0:
|
|
others += 1
|
|
check(others >= 5, "and other players' X are mostly somewhere else (%d of 8)" % others)
|
|
check(w.treasure_spot(me) == x, "(the same every time for you)")
|
|
check(w.map_marks().any(func(m): return m.kind == "treasure" and m.pos == x), "on your map while you carry it, even where you haven't been")
|
|
_toasts.clear()
|
|
w.read_map(p)
|
|
check(_toasts.any(func(s): return String(s).begins_with("X marks the spot")), "reading it: X marks the spot")
|
|
p.inventory.remove("treasure_map", 1)
|
|
check(not w.map_marks().any(func(m): return m.kind == "treasure"), "(no map, no X)")
|
|
p.inventory.add("treasure_map", 1)
|
|
w.explore(wk.global_position)
|
|
check(w.map_marks().any(func(m): return m.kind == "wreck"), "the wreck's on the map once you've seen it")
|
|
|
|
print("the dig")
|
|
var tr: Node3D = w.treasure
|
|
check(tr != null, "the Cove has the pirate treasure")
|
|
for _i in range(3):
|
|
await process_frame
|
|
await create_timer(0.6).timeout
|
|
var m: Node3D = tr.mounds.get(me)
|
|
check(m != null and m.global_position.distance_to(x) < 0.1, "with your map, a mound of loose earth at your X")
|
|
check(tr.mounds.size() == 1, "(only yours: nobody else's shows)")
|
|
if m == null:
|
|
return
|
|
p.inventory.add("stone_pickaxe", 1)
|
|
_hold(p, "stone_pickaxe")
|
|
tr.force_crew = true
|
|
w.day_night.time = 21.0 / 24.0 # night
|
|
w.day_night.apply(true)
|
|
p.global_position = m.global_position + Vector3(1.4, 0, 0)
|
|
p.survival.stamina = 100.0
|
|
A.harvest_hit(p, m, m.global_position)
|
|
check(tr.crews.has(me) and tr.crews[me].alive().size() == 3, "at night, the first blow brings up the drowned crew (%d)" % (tr.crews[me].alive().size() if tr.crews.has(me) else 0))
|
|
if tr.crews.has(me):
|
|
var g: Node3D = tr.crews[me].alive()[0]
|
|
check(g.kind == "drowned" and g.state == "charge", "drowned sailors, coming for you")
|
|
g.set_physics_process(false)
|
|
for c in tr.crews[me].alive():
|
|
c.queue_free()
|
|
tr.crews[me].queue_free()
|
|
await process_frame
|
|
var blows := 1
|
|
while is_instance_valid(m) and not m.depleted and blows < 40:
|
|
p.survival.stamina = 100.0
|
|
A.harvest_hit(p, m, m.global_position)
|
|
blows += 1
|
|
check(blows == 18, "18 blows of a stone pickaxe (%d)" % blows)
|
|
check(p.inventory.count("ship_compass") == 1 and p.inventory.count("spyglass") == 1 and p.inventory.count("cutlass") == 1 \
|
|
and p.inventory.count("pirate_gold") == 3, "the pirate chest: a ship's compass, a spyglass, a cutlass and 3 pirate gold")
|
|
check(p.inventory.count("treasure_map") == 0, "and the map's used up")
|
|
check(me in tr.dug and not tr.mounds.has(me), "your treasure's dug: no more mound")
|
|
check(not w.map_marks().any(func(mk): return mk.kind == "treasure"), "(and no X)")
|
|
_hold(p, "cutlass")
|
|
var ms: Dictionary = A.melee_stats(p)
|
|
check(bool(ms.armed) and float(ms.damage) >= 30.0, "the cutlass hits hard (%s)" % ms.damage)
|
|
var other: Node3D = tr.mound_for("someone-else")
|
|
check(other != null and other.global_position.distance_to(x) > 1.0, "someone else's treasure is still in the ground, somewhere else")
|
|
check(DB_has("pirate_gold") and A.can_place(p, _slot(p, "pirate_gold")), "pirate gold can be set down as a trophy")
|
|
|
|
print("saved")
|
|
w.save_now()
|
|
await _drop(w)
|
|
w = await _load()
|
|
check(w.wreck.had(me) and w.wreck.had("someone-else") and not w.wreck.had("a-stranger"), "who's had their share is kept")
|
|
check(me in w.treasure.dug, "and whose treasure's been dug")
|
|
await _drop(w)
|
|
_finished = true
|
|
|
|
func DB_has(id: String) -> bool:
|
|
return not root.get_node("ItemDB").get_item(id).is_empty()
|
|
|
|
func _slot(p: Node, id: String) -> int:
|
|
for i in range(p.inventory.size()):
|
|
var s = p.inventory.get_slot(i)
|
|
if s != null and s.id == id:
|
|
return i
|
|
return -1
|
|
|
|
func _hold(p: Node, id: String) -> void:
|
|
var i := _slot(p, id)
|
|
if i < 0:
|
|
return
|
|
var tmp = p.inventory.get_slot(0)
|
|
p.inventory.set_slot(0, p.inventory.get_slot(i))
|
|
p.inventory.set_slot(i, tmp)
|
|
p.select(0)
|