212 lines
11 KiB
GDScript
212 lines
11 KiB
GDScript
extends SceneTree
|
|
## Gitea #7: growing a house. A longer one (along the ridge): take out the
|
|
## end wall and gables, add floors, walls and a roof bay, and the gables go
|
|
## on the new end. A wider one (across the ridge): the roof gets a second row
|
|
## of panels on top of the first, and its taller ends close with a half wall
|
|
## under a gable. Inside both you're under a roof and sheltered.
|
|
## Run: godot --headless --path . --script res://tests/test_roofs.gd
|
|
|
|
var _fails := 0
|
|
var _finished := false
|
|
var S: Node
|
|
var w: Node3D
|
|
var B: Node
|
|
const Q := PI * 0.5
|
|
|
|
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")
|
|
S.root_dir = "user://test_roofs_worlds"
|
|
for x in S.list_worlds():
|
|
S.delete_world(x.id)
|
|
S.current_id = S.create_world("Roofs", 1337)
|
|
w = 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)
|
|
B = w.build
|
|
await _longer()
|
|
await _wider()
|
|
await _six()
|
|
_finished = true
|
|
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)
|
|
|
|
## One piece, aimed as a player would (build_system.gd's hut helper).
|
|
func _place(out: Dictionary, c: Vector3, id: String, local: Vector3, ryaw: float, on: Node3D) -> Node3D:
|
|
return await B._hut_place(out, c, 0.0, id, local, ryaw, on)
|
|
|
|
func _new_out() -> Dictionary:
|
|
return {"placed": 0, "failed": [], "pieces": []}
|
|
|
|
func _longer() -> void:
|
|
print("a longer house (along the ridge)")
|
|
var c: Vector3 = B.debug_find_site(w._spawn + Vector3(20, 0, 0), 5.0)
|
|
var hut: Dictionary = await B.debug_build_hut(c, 0.0)
|
|
check(hut.failed.is_empty(), "the 4 x 4 hut (%d pieces)" % hut.placed)
|
|
var f0: Node3D = hut.pieces[0]
|
|
var top: float = f0.global_position.y - c.y
|
|
for p in hut.pieces.duplicate():
|
|
var l: Vector3 = p.global_position - c
|
|
if (p.kind == "wall" or p.kind == "gable") and l.x > 1.5:
|
|
w.remove_piece(p)
|
|
for _i in range(3):
|
|
await physics_frame
|
|
var out := _new_out()
|
|
for z in [-1.0, 1.0]:
|
|
await _place(out, c, "wood_floor", Vector3(3, 0, z), 0.0, null)
|
|
await _place(out, c, "wood_wall", Vector3(3, top, -1.9), 0.0, f0)
|
|
await _place(out, c, "wood_wall", Vector3(3, top, 1.9), 0.0, f0)
|
|
for z in [-1.0, 1.0]:
|
|
await _place(out, c, "wood_wall", Vector3(3.9, top, z), Q, f0)
|
|
await _place(out, c, "wood_roof", Vector3(3, top + 2.0, 2.0), 0.0, f0)
|
|
await _place(out, c, "wood_roof", Vector3(3, top + 2.0, -2.0), PI, f0)
|
|
await _place(out, c, "wood_gable", Vector3(4, top + 2.0, 1.0), Q, f0)
|
|
await _place(out, c, "wood_gable", Vector3(4, top + 2.0, -1.0), -Q, f0)
|
|
check(out.failed.is_empty() and out.placed == 10, "a bay on the end, and its gables (%d placed%s)" % [out.placed, "" if out.failed.is_empty() else ": %s" % [out.failed]])
|
|
var inside := Vector3(c.x + 3.0, f0.global_position.y, c.z)
|
|
check(B.roofed_at(inside) and B.sheltered_at(inside), "sheltered in the new end")
|
|
|
|
func _wider() -> void:
|
|
print("a wider house (across the ridge): 4 m along, 8 m across")
|
|
var c := Vector3.INF
|
|
for near in [Vector3(-30, 0, 0), Vector3(0, 0, 30), Vector3(30, 0, 25), Vector3(-25, 0, -25), Vector3(0, 0, -35)]:
|
|
var at: Vector3 = w._spawn + near
|
|
var got: Vector3 = B.debug_find_site(at, 5.0)
|
|
if got != at: # (it hands back where you asked when there's no site)
|
|
c = got
|
|
break
|
|
check(c != Vector3.INF, "(a site for it)")
|
|
if c == Vector3.INF:
|
|
return
|
|
var out := _new_out()
|
|
# Floors: 2 x 4. The first sits on the ground, the rest snap to it.
|
|
var f0: Node3D = await _place(out, c, "wood_floor", Vector3(-1, 0, -3), 0.0, null)
|
|
check(f0 != null, "(the first floor: %s)" % [out.failed])
|
|
if f0 == null:
|
|
return
|
|
var top: float = f0.global_position.y - c.y
|
|
for x in [-1.0, 1.0]:
|
|
for z in [-3.0, -1.0, 1.0, 3.0]:
|
|
if x == -1.0 and z == -3.0:
|
|
continue
|
|
await _place(out, c, "wood_floor", Vector3(x, 0, z), 0.0, null)
|
|
# Walls all round.
|
|
for x in [-1.0, 1.0]:
|
|
await _place(out, c, "wood_wall", Vector3(x, top, -3.9), 0.0, f0)
|
|
await _place(out, c, "wood_wall", Vector3(x, top, 3.9), 0.0, f0)
|
|
for z in [-3.0, -1.0, 1.0, 3.0]:
|
|
await _place(out, c, "wood_wall", Vector3(1.9, top, z), Q, f0)
|
|
await _place(out, c, "wood_wall", Vector3(-1.9, top, z), Q, f0)
|
|
check(out.failed.is_empty(), "floors and walls (%d placed%s)" % [out.placed, "" if out.failed.is_empty() else ": %s" % [out.failed]])
|
|
var n0: int = out.placed
|
|
# The roof: the first row on the long walls, the second row on top of it,
|
|
# meeting at the ridge 2 m up.
|
|
for x in [-1.0, 1.0]:
|
|
await _place(out, c, "wood_roof", Vector3(x, top + 2.0, 4.0), 0.0, f0)
|
|
await _place(out, c, "wood_roof", Vector3(x, top + 2.0, -4.0), PI, f0)
|
|
var n1: int = out.placed
|
|
for x in [-1.0, 1.0]:
|
|
await _place(out, c, "wood_roof", Vector3(x, top + 3.0, 2.0), 0.0, f0)
|
|
await _place(out, c, "wood_roof", Vector3(x, top + 3.0, -2.0), PI, f0)
|
|
check(out.placed - n1 == 4 and out.failed.is_empty(), "a second row of roof on top of the first, up to the ridge (%d placed%s)" % [out.placed - n1, "" if out.failed.is_empty() else ": %s" % [out.failed]])
|
|
var ridge: Array = out.pieces.filter(func(p): return p.kind == "roof" and absf(p.global_position.y - (f0.global_position.y + 3.0)) < 0.2)
|
|
check(ridge.size() == 4 and ridge.all(func(p): return p.support >= B.MIN_SUPPORT), "(held up: support %s)" % [ridge.map(func(p): return snappedf(p.support, 0.01))])
|
|
# The ends: a gable on the wall at the eaves; nearer the ridge, a half
|
|
# wall on the wall and a gable on the half wall.
|
|
var n2: int = out.placed
|
|
for x in [-2.0, 2.0]:
|
|
await _place(out, c, "wood_gable", Vector3(x, top + 2.0, 3.0), Q, f0)
|
|
await _place(out, c, "wood_gable", Vector3(x, top + 2.0, -3.0), -Q, f0)
|
|
await _place(out, c, "wood_half_wall", Vector3(x, top + 2.0, 1.0), Q, f0)
|
|
await _place(out, c, "wood_half_wall", Vector3(x, top + 2.0, -1.0), Q, f0)
|
|
await _place(out, c, "wood_gable", Vector3(x, top + 3.0, 1.0), Q, f0)
|
|
await _place(out, c, "wood_gable", Vector3(x, top + 3.0, -1.0), -Q, f0)
|
|
check(out.placed - n2 == 12 and out.failed.is_empty(), "both ends closed: gables, and half walls under the upper gables (%d placed%s)" % [out.placed - n2, "" if out.failed.is_empty() else ": %s" % [out.failed]])
|
|
var halves: Array = out.pieces.filter(func(p): return p.kind == "half_wall")
|
|
check(halves.size() == 4 and halves.all(func(p): return absf(p.global_position.y - (f0.global_position.y + 2.0)) < 0.05),
|
|
"the half walls stand on the walls' tops")
|
|
var uppers: Array = out.pieces.filter(func(p): return p.kind == "gable" and absf(p.global_position.y - (f0.global_position.y + 3.0)) < 0.05)
|
|
check(uppers.size() == 4, "and the upper gables stand on the half walls (%d)" % uppers.size())
|
|
for z in [-3.0, 0.0, 3.0]:
|
|
var inside := Vector3(c.x, f0.global_position.y, c.z + z)
|
|
check(B.roofed_at(inside) and B.sheltered_at(inside), "sheltered inside, %+.0f m across (roof %s, walls %.2f)" % [z, B.roofed_at(inside), B.enclosure_at(inside)])
|
|
# A half wall on its own: a low wall on a floor.
|
|
var out2 := _new_out()
|
|
var c2: Vector3 = B.debug_find_site(w._spawn + Vector3(-25, 0, 10), 3.0)
|
|
var g0: Node3D = await _place(out2, c2, "wood_floor", Vector3(0, 0, 0), 0.0, null)
|
|
if g0 != null:
|
|
var t2: float = g0.global_position.y - c2.y
|
|
var hw: Node3D = await _place(out2, c2, "wood_half_wall", Vector3(0, t2, 0.9), 0.0, g0)
|
|
check(hw != null and absf(hw.global_position.y - g0.global_position.y) < 0.05, "a half wall on a floor's edge: a low wall (%s)" % [out2.failed])
|
|
|
|
## A site with room for a house `half` metres each way from its middle.
|
|
func _site(half: float, tries: Array) -> Vector3:
|
|
for near in tries:
|
|
var at: Vector3 = w._spawn + near
|
|
var got: Vector3 = B.debug_find_site(at, half)
|
|
if got != at:
|
|
return got
|
|
return Vector3.INF
|
|
|
|
func _six() -> void:
|
|
print("a 6 x 6 house: a roof ridge bridges the middle")
|
|
var c := _site(4.0, [Vector3(30, 0, -20), Vector3(-30, 0, 20), Vector3(25, 0, 30), Vector3(-35, 0, -10)])
|
|
check(c != Vector3.INF, "(a site for it)")
|
|
if c == Vector3.INF:
|
|
return
|
|
var out := _new_out()
|
|
var f0: Node3D = await _place(out, c, "wood_floor", Vector3(-2, 0, -2), 0.0, null)
|
|
if f0 == null:
|
|
check(false, "(the first floor: %s)" % [out.failed])
|
|
return
|
|
var top: float = f0.global_position.y - c.y
|
|
for x in [-2.0, 0.0, 2.0]:
|
|
for z in [-2.0, 0.0, 2.0]:
|
|
if not (x == -2.0 and z == -2.0):
|
|
await _place(out, c, "wood_floor", Vector3(x, 0, z), 0.0, null)
|
|
for x in [-2.0, 0.0, 2.0]:
|
|
await _place(out, c, "wood_wall", Vector3(x, top, -2.9), 0.0, f0)
|
|
await _place(out, c, "wood_wall", Vector3(x, top, 2.9), 0.0, f0)
|
|
for z in [-2.0, 0.0, 2.0]:
|
|
await _place(out, c, "wood_wall", Vector3(2.9, top, z), Q, f0)
|
|
await _place(out, c, "wood_wall", Vector3(-2.9, top, z), Q, f0)
|
|
check(out.failed.is_empty() and out.placed == 21, "9 floors and 12 walls (%d placed%s)" % [out.placed, "" if out.failed.is_empty() else ": %s" % [out.failed]])
|
|
var n0: int = out.placed
|
|
for x in [-2.0, 0.0, 2.0]:
|
|
await _place(out, c, "wood_roof", Vector3(x, top + 2.0, 3.0), 0.0, f0)
|
|
await _place(out, c, "wood_roof", Vector3(x, top + 2.0, -3.0), PI, f0)
|
|
check(out.placed - n0 == 6, "a row of roof up from each long wall, leaving a 2 m gap in the middle (%d%s)" % [out.placed - n0, "" if out.failed.is_empty() else ": %s" % [out.failed]])
|
|
var n1: int = out.placed
|
|
for x in [-2.0, 0.0, 2.0]:
|
|
await _place(out, c, "wood_roof_ridge", Vector3(x, top + 3.0, 0.0), 0.0, f0)
|
|
check(out.placed - n1 == 3 and out.failed.is_empty(), "roof ridges bridge it (%d placed%s)" % [out.placed - n1, "" if out.failed.is_empty() else ": %s" % [out.failed]])
|
|
var ridges: Array = out.pieces.filter(func(p): return p.kind == "roof_ridge")
|
|
check(ridges.size() == 3 and ridges.all(func(p): return absf(p.global_position.y - (f0.global_position.y + 3.0)) < 0.05 and absf(p.global_position.z - c.z) < 0.05),
|
|
"on the rows' top edges, along the middle")
|
|
check(ridges.all(func(p): return p.support >= B.MIN_SUPPORT), "(held up: support %s)" % [ridges.map(func(p): return snappedf(p.support, 0.01))])
|
|
var n2: int = out.placed
|
|
for x in [-3.0, 3.0]:
|
|
await _place(out, c, "wood_gable", Vector3(x, top + 2.0, 2.0), Q, f0)
|
|
await _place(out, c, "wood_gable", Vector3(x, top + 2.0, -2.0), -Q, f0)
|
|
await _place(out, c, "wood_half_wall", Vector3(x, top + 2.0, 0.0), Q, f0)
|
|
await _place(out, c, "wood_ridge_gable", Vector3(x, top + 3.0, 0.0), Q, f0)
|
|
check(out.placed - n2 == 8 and out.failed.is_empty(), "the ends closed: gables, and a half wall and ridge gable in the middle (%d placed%s)" % [out.placed - n2, "" if out.failed.is_empty() else ": %s" % [out.failed]])
|
|
for z in [-2.0, 0.0, 2.0]:
|
|
var inside := Vector3(c.x, f0.global_position.y, c.z + z)
|
|
check(B.roofed_at(inside) and B.sheltered_at(inside), "sheltered inside, %+.0f m across (roof %s, walls %.2f)" % [z, B.roofed_at(inside), B.enclosure_at(inside)])
|