Bigger houses (Gitea #7): a second row of roof on a wider house, a Roof Ridge for a 6 m-wide one, and a Half Wall and Ridge Gable to close their taller ends; also no error when a piece is dismantled straight after it's placed
This commit is contained in:
@@ -280,13 +280,49 @@
|
||||
"name": "Wood Gable",
|
||||
"category": "building",
|
||||
"stack": 20,
|
||||
"description": "Triangle that closes the end of a roof. R to flip it.",
|
||||
"description": "Triangle that closes the end of a roof. R to flip it. On a wider house, stand it on a half wall to close a taller roof's end.",
|
||||
"mesh": "wood_gable",
|
||||
"place": {
|
||||
"piece": "build",
|
||||
"kind": "gable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wood_half_wall",
|
||||
"name": "Wood Half Wall",
|
||||
"category": "building",
|
||||
"stack": 20,
|
||||
"description": "2 x 1 m. A low wall or railing, or on top of a wall, under a gable, to close the end of a wider house's taller roof.",
|
||||
"mesh": "wood_half_wall",
|
||||
"place": {
|
||||
"piece": "build",
|
||||
"kind": "half_wall"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wood_roof_ridge",
|
||||
"name": "Wood Roof Ridge",
|
||||
"category": "building",
|
||||
"stack": 20,
|
||||
"description": "A 2 m ridge cap: on a 6 m-wide house, it bridges the gap between the two sides' roofs, at the top.",
|
||||
"mesh": "wood_roof_ridge",
|
||||
"place": {
|
||||
"piece": "build",
|
||||
"kind": "roof_ridge"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wood_ridge_gable",
|
||||
"name": "Wood Ridge Gable",
|
||||
"category": "building",
|
||||
"stack": 20,
|
||||
"description": "A small triangle that closes the end under a roof ridge. Stand it on a half wall.",
|
||||
"mesh": "wood_ridge_gable",
|
||||
"place": {
|
||||
"piece": "build",
|
||||
"kind": "ridge_gable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bed",
|
||||
"name": "Bed",
|
||||
|
||||
@@ -138,6 +138,37 @@
|
||||
"wood": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wood_half_wall",
|
||||
"output": "wood_half_wall",
|
||||
"count": 1,
|
||||
"station": "workbench",
|
||||
"category": "building",
|
||||
"inputs": {
|
||||
"wood": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wood_roof_ridge",
|
||||
"output": "wood_roof_ridge",
|
||||
"count": 1,
|
||||
"station": "workbench",
|
||||
"category": "building",
|
||||
"inputs": {
|
||||
"wood": 2,
|
||||
"fiber": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wood_ridge_gable",
|
||||
"output": "wood_ridge_gable",
|
||||
"count": 1,
|
||||
"station": "workbench",
|
||||
"category": "building",
|
||||
"inputs": {
|
||||
"wood": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bed",
|
||||
"output": "bed",
|
||||
|
||||
@@ -61,6 +61,15 @@ static func _define() -> Dictionary:
|
||||
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0)],
|
||||
"center": Vector3(0.33, 0.33, 0),
|
||||
}
|
||||
# Half wall: 2 m wide, 1 m tall (0.6.2, Gitea #7). On a wall's top, under
|
||||
# a gable, it closes the end of a taller roof (a wider house, with a
|
||||
# second row of roof panels); on a floor, it's a low wall or a railing.
|
||||
k["half_wall"] = {
|
||||
"shapes": [_box(Vector3(0, 0.5, 0), Vector3(2, 1, 0.2))],
|
||||
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0),
|
||||
Vector3(-1, 1, 0), Vector3(0, 1, 0), Vector3(1, 1, 0)],
|
||||
"center": Vector3(0, 0.5, 0),
|
||||
}
|
||||
# --- P2 (docs/PROGRESSION_DESIGN.md §3) ---
|
||||
# Pillar: a 0.4 m post standing on a floor corner or edge, carrying what's
|
||||
# on top (a floor's corner meets its top).
|
||||
@@ -95,6 +104,29 @@ static func _define() -> Dictionary:
|
||||
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0)],
|
||||
"center": Vector3(0, 1.5, 0),
|
||||
}
|
||||
# Roof ridge (0.6.2, Gitea #7): a 2 m cap, an upside-down V at the roof's
|
||||
# slope, for houses whose width leaves a 2 m gap between the two sides'
|
||||
# top rows (6 m, 10 m). Origin = centre at the level of the rows' top
|
||||
# edges (local z = +-1), rising 0.5 m to the peak along z = 0.
|
||||
var ns := Vector3(0, cos(ROOF_ANGLE), sin(ROOF_ANGLE))
|
||||
var nn := Vector3(0, cos(ROOF_ANGLE), -sin(ROOF_ANGLE))
|
||||
var half := sqrt(1.25)
|
||||
k["roof_ridge"] = {
|
||||
"shapes": [
|
||||
{"type": "box", "center": Vector3(0, 0.25, 0.5) + ns * ROOF_T * 0.5, "size": Vector3(2, ROOF_T, half), "basis": Basis(Vector3.RIGHT, ROOF_ANGLE)},
|
||||
{"type": "box", "center": Vector3(0, 0.25, -0.5) + nn * ROOF_T * 0.5, "size": Vector3(2, ROOF_T, half), "basis": Basis(Vector3.RIGHT, -ROOF_ANGLE)},
|
||||
_box(Vector3(0, 0.55, 0), Vector3(2, 0.08, 0.3))], # (the ridge board: no gap at the peak for rain, or the shelter test)
|
||||
"snaps": [Vector3(-1, 0, 1), Vector3(0, 0, 1), Vector3(1, 0, 1),
|
||||
Vector3(-1, 0, -1), Vector3(0, 0, -1), Vector3(1, 0, -1)],
|
||||
"center": Vector3(0, 0.25, 0),
|
||||
}
|
||||
# Ridge gable: the small triangle (2 m wide, 0.5 m tall, peak in the
|
||||
# middle) that closes the end under a roof ridge, on a half wall or a wall.
|
||||
k["ridge_gable"] = {
|
||||
"shapes": [_prism([Vector2(-1, 0), Vector2(1, 0), Vector2(0, 0.5)], 0.2)],
|
||||
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 0.5, 0)],
|
||||
"center": Vector3(0, 0.17, 0),
|
||||
}
|
||||
var n := Vector3(0, cos(ROOF_ANGLE), sin(ROOF_ANGLE)) # panel normal (up and out)
|
||||
# (Ends exactly at the ridge. The seam where two roofs meet is handled by
|
||||
# build_system.roofed_at sampling several rays.)
|
||||
|
||||
@@ -69,6 +69,9 @@ static func build(key: String) -> ArrayMesh:
|
||||
"wood_door_panel": m = _door_panel()
|
||||
"wood_roof": m = _roof()
|
||||
"wood_gable": m = _gable()
|
||||
"wood_half_wall": m = _half_wall()
|
||||
"wood_roof_ridge": m = _roof_ridge()
|
||||
"wood_ridge_gable": m = _ridge_gable()
|
||||
"bed": m = _bed()
|
||||
"raw_meat": m = _meat(MEAT_RAW)
|
||||
"cooked_meat": m = _meat(MEAT_COOKED)
|
||||
@@ -546,6 +549,17 @@ static func _wall() -> ArrayMesh:
|
||||
_box(st, Vector3(0, y, 0), Vector3(1.68, 0.12, 0.2), BEAM)
|
||||
return P._commit(st)
|
||||
|
||||
## Half wall: the wall's boards and frame, 2 x 1 m.
|
||||
static func _half_wall() -> ArrayMesh:
|
||||
var st := P._begin()
|
||||
var r := _rng(75)
|
||||
_wall_body(st, r, -0.84, 0.84, 0.1, 0.9)
|
||||
for x in [-0.92, 0.92]:
|
||||
_box(st, Vector3(x, 0.5, 0), Vector3(0.16, 1.0, 0.2), BEAM)
|
||||
for y in [0.06, 0.94]:
|
||||
_box(st, Vector3(0, y, 0), Vector3(1.68, 0.12, 0.2), BEAM)
|
||||
return P._commit(st)
|
||||
|
||||
## Door frame (1.1 x 1.9 m opening); `with_panel` adds the closed door, for the
|
||||
## inventory icon and the placement ghost.
|
||||
static func _door(with_panel: bool) -> ArrayMesh:
|
||||
@@ -597,6 +611,37 @@ static func _roof() -> ArrayMesh:
|
||||
_box(st, along * s + n * 0.085, Vector3(2.04, 0.05, (length + 0.3) / rows + 0.1), _board(r, SHINGLE), tilt)
|
||||
return P._commit(st)
|
||||
|
||||
## Roof ridge: two short shingled panels meeting at a peak 0.5 m up (an
|
||||
## upside-down V across local Z), origin at the centre of the low edges'
|
||||
## level. Bridges the gap between two roof rows on a 6 m-wide house.
|
||||
static func _roof_ridge() -> ArrayMesh:
|
||||
var st := P._begin()
|
||||
var r := _rng(91)
|
||||
var a := 0.4636476
|
||||
var half := sqrt(1.25)
|
||||
for side in [1.0, -1.0]:
|
||||
var b := Basis(Vector3.RIGHT, a * side)
|
||||
var n := Vector3(0, cos(a), sin(a) * side)
|
||||
var mid := Vector3(0, 0.25, 0.5 * side)
|
||||
_box(st, mid + n * 0.03, Vector3(2.0, 0.06, half + 0.06), _board(r, BEAM), b)
|
||||
for i in range(2):
|
||||
var along := Vector3(0, -sin(a), cos(a) * side) * (half * (0.25 - 0.5 * i))
|
||||
_box(st, mid + along + n * 0.085, Vector3(2.04, 0.05, half * 0.5 + 0.1), _board(r, SHINGLE), Basis(Vector3.RIGHT, (a + 0.06) * side))
|
||||
_box(st, Vector3(0, 0.55, 0), Vector3(2.06, 0.08, 0.16), BEAM) # (the ridge board)
|
||||
return P._commit(st)
|
||||
|
||||
## Ridge gable: a small triangle, 2 m wide and 0.5 m tall, peak in the middle.
|
||||
static func _ridge_gable() -> ArrayMesh:
|
||||
var st := P._begin()
|
||||
var r := _rng(98)
|
||||
for i in range(2):
|
||||
var y0 := 0.25 * i
|
||||
var hw := 0.92 * (1.0 - y0 / 0.5) - 0.06
|
||||
_box(st, Vector3(0, y0 + 0.125, 0), Vector3(hw * 2.0, 0.25 - 0.015, 0.12), _board(r))
|
||||
_box(st, Vector3(0, 0.06, 0), Vector3(2.0, 0.12, 0.2), BEAM)
|
||||
_box(st, Vector3(0, 0.25, 0), Vector3(0.14, 0.5, 0.2), BEAM)
|
||||
return P._commit(st)
|
||||
|
||||
## Triangular gable end: 0 m tall at -X, 1 m tall at +X.
|
||||
static func _gable() -> ArrayMesh:
|
||||
var st := P._begin()
|
||||
|
||||
@@ -673,7 +673,7 @@ func _wall_hit(space: PhysicsDirectSpaceState3D, a: Vector3, b: Vector3) -> bool
|
||||
var piece: Node = c as Node
|
||||
while piece != null and piece.get("kind") == null:
|
||||
piece = piece.get_parent()
|
||||
if piece != null and String(piece.kind) in ["wall", "door", "gable", "window", "palisade", "pillar"]:
|
||||
if piece != null and String(piece.kind) in ["wall", "half_wall", "door", "gable", "ridge_gable", "window", "palisade", "pillar"]:
|
||||
return true
|
||||
ex.append(hit.rid) # a floor or roof edge: look past it
|
||||
return false
|
||||
|
||||
@@ -173,8 +173,8 @@ func clear_under(piece: Node3D) -> void:
|
||||
if not is_instance_valid(piece) or not is_inside_tree():
|
||||
return
|
||||
await get_tree().physics_frame # (the piece's collider is in the world from the next step)
|
||||
if not is_instance_valid(piece):
|
||||
return
|
||||
if not is_instance_valid(piece) or not piece.is_inside_tree():
|
||||
return # (dismantled straight away)
|
||||
var space := get_world_3d().direct_space_state
|
||||
var at := piece.global_position
|
||||
for pk in pickups.values():
|
||||
|
||||
@@ -9,7 +9,7 @@ if [ ! -f .godot/global_script_class_cache.cfg ]; then
|
||||
echo "Importing the project first (fresh copy)..."
|
||||
godot --headless --path . --import >/dev/null 2>&1
|
||||
fi
|
||||
for t in test_boot test_survival_core test_survival_stats test_save_load test_weather_building test_creatures_combat test_cliffs test_wolves test_alpha test_bear test_thieves test_barrow test_boss test_den_sites test_fishing_diet test_food test_hazards test_iron test_stone test_net_lobby test_net_world test_net_shared test_net_life test_net_boss test_net_rules test_net_lands test_host test_tutorial test_tutorial_full test_hints test_updates test_audio test_items_more test_worldlets test_worldlet_pick test_water_bodies test_cove test_wreck test_shore test_boat test_bug_report test_tunnel test_map test_terrain_edges test_boundary; do
|
||||
for t in test_boot test_survival_core test_survival_stats test_save_load test_weather_building test_roofs test_creatures_combat test_cliffs test_wolves test_alpha test_bear test_thieves test_barrow test_boss test_den_sites test_fishing_diet test_food test_hazards test_iron test_stone test_net_lobby test_net_world test_net_shared test_net_life test_net_boss test_net_rules test_net_lands test_host test_tutorial test_tutorial_full test_hints test_updates test_audio test_items_more test_worldlets test_worldlet_pick test_water_bodies test_cove test_wreck test_shore test_boat test_bug_report test_tunnel test_map test_terrain_edges test_boundary; do
|
||||
extra=""
|
||||
[ "$t" = "test_boundary" ] && extra="--fixed-fps 30"
|
||||
# A suite that waits on a world that failed to load would hang forever,
|
||||
|
||||
211
tests/test_roofs.gd
Normal file
211
tests/test_roofs.gd
Normal file
@@ -0,0 +1,211 @@
|
||||
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)])
|
||||
1
tests/test_roofs.gd.uid
Normal file
1
tests/test_roofs.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ceh70qtahkoa0
|
||||
@@ -181,6 +181,8 @@ Select a piece on your hotbar and a ghost shows where it will go: green if it fi
|
||||
|
||||
Building pieces (floors, walls, roofs, gables, doors, windows, stairs, posts, palisades) are 2 m on a side and **snap** to each other: aim near a piece and the ghost lines up with it. On open ground a floor sits level and a wall sits flush. You can't build in water. A roof rises 1 m over 2 m, so two from opposite walls of a 4 m hut meet at the ridge; gables close the ends.
|
||||
|
||||
To make a house **longer**, add bays along the ridge: more roof, and the gables go on the new end. To make it **wider**, the roof climbs higher: on an 8 m-wide house, a second row of roof sits on top of the first; on a 6 m-wide one, a **Roof Ridge** bridges the 2 m gap at the top. The taller ends close with a **Half Wall** (2 m x 1 m) on top of the wall, then a gable on the half wall, or a small **Ridge Gable** under a roof ridge. A half wall on its own makes a low wall or a railing.
|
||||
|
||||
### Support
|
||||
|
||||
Look at a piece to see its **support**. Anything on the ground has full support; each piece above or beside takes support from the best piece it's joined to, losing a little going up and a lot going sideways. Below 30% a piece can't be placed, and pieces left without support collapse back into your inventory. A hut, a second storey or a short overhang works; a bridge in mid-air doesn't. Posts and pillars carry their load straight down.
|
||||
|
||||
Reference in New Issue
Block a user