extends "res://scripts/building/piece.gd" ## A snap-together building piece: wood floor, wall, roof, gable or door frame. ## ## Geometry lives here (not in items.json) because it has to match the meshes ## in item_meshes.gd exactly: ## - `shapes`: the colliders, also used (shrunk slightly) for overlap checks. ## - `snaps`: snap points in local space. Two pieces are connected when a snap ## point of one coincides with a snap point of the other; build_system.gd ## uses that for snapping and for structural support. ## - `center`: rough centre of mass, used to tell vertical from sideways ## support. ## ## All pieces are 2 m on a side so they line up on a 2 m grid. ## Origins: floor = centre of the top surface; wall, door and gable = bottom ## centre; roof = centre of the low edge, rising 1 m over 2 m toward -Z ## (about 27 degrees), so two roofs from opposite walls of a 4 m hut meet at a ## ridge. const BUILD_LAYER := 4 ## physics layer 3: shelter + regrowth queries const ROOF_ANGLE := 0.4636476 ## atan(1 / 2) const ROOF_T := 0.12 ## roof panel thickness static var _kinds := {} var kind := "wall" var support := 1.0 ## 0..1, set by build_system.gd var grounded := false static func kinds() -> Dictionary: if _kinds.is_empty(): _kinds = _define() return _kinds static func _define() -> Dictionary: var k := {} var wall_snaps := [ Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(-1, 2, 0), Vector3(0, 2, 0), Vector3(1, 2, 0)] k["floor"] = { "shapes": [_box(Vector3(0, -0.1, 0), Vector3(2, 0.2, 2))], "snaps": [Vector3(-1, 0, -1), Vector3(0, 0, -1), Vector3(1, 0, -1), Vector3(-1, 0, 0), Vector3(1, 0, 0), Vector3(-1, 0, 1), Vector3(0, 0, 1), Vector3(1, 0, 1)], "center": Vector3(0, -0.1, 0), } k["wall"] = { "shapes": [_box(Vector3(0, 1, 0), Vector3(2, 2, 0.2))], "snaps": wall_snaps, "center": Vector3(0, 1, 0), } # Door frame: 1.1 m x 1.9 m opening (the player is 0.8 m x 1.8 m). k["door"] = { "shapes": [_box(Vector3(-0.775, 1, 0), Vector3(0.45, 2, 0.2)), _box(Vector3(0.775, 1, 0), Vector3(0.45, 2, 0.2)), _box(Vector3(0, 1.95, 0), Vector3(1.1, 0.1, 0.2))], "snaps": wall_snaps, "center": Vector3(0, 1, 0), } # Gable: right triangle, 0 m tall at -X, 1 m at +X (fills the end of a # roof; rotate 180 degrees for the other half). k["gable"] = { "shapes": [_prism([Vector2(-1, 0), Vector2(1, 0), Vector2(1, 1)], 0.2)], "snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0)], "center": Vector3(0.33, 0.33, 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.) k["roof"] = { "shapes": [{"type": "box", "center": Vector3(0, 0.5, -1) + n * ROOF_T * 0.5, "size": Vector3(2, ROOF_T, sqrt(5.0)), "basis": Basis(Vector3.RIGHT, ROOF_ANGLE)}], "snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(-1, 1, -2), Vector3(0, 1, -2), Vector3(1, 1, -2)], "center": Vector3(0, 0.5, -1), } return k static func _box(c: Vector3, s: Vector3) -> Dictionary: return {"type": "box", "center": c, "size": s, "basis": Basis()} ## A prism: a 2D outline in the XY plane, extruded `t` along Z (centred). static func _prism(outline: Array, t: float) -> Dictionary: return {"type": "prism", "outline": outline, "thickness": t} ## Builds a Shape3D for one shape entry, shrunk by `margin` on every side ## (margin > 0 is for overlap tests, so touching neighbours don't count). ## Returns [shape, local transform]. static func make_shape(s: Dictionary, margin: float = 0.0) -> Array: if s.type == "box": var b := BoxShape3D.new() var size: Vector3 = s.size b.size = Vector3(maxf(size.x - margin * 2.0, 0.02), maxf(size.y - margin * 2.0, 0.02), maxf(size.z - margin * 2.0, 0.02)) return [b, Transform3D(s.basis, s.center)] var outline: Array = s.outline var c := Vector2.ZERO for p in outline: c += p c /= outline.size() var pts := PackedVector3Array() var hz := maxf(float(s.thickness) * 0.5 - margin, 0.01) for p: Vector2 in outline: var q := p if margin > 0.0: q = c + (p - c) * (1.0 - margin * 2.0 / maxf((p - c).length(), 0.01)) pts.append(Vector3(q.x, q.y, -hz)) pts.append(Vector3(q.x, q.y, hz)) var cv := ConvexPolygonShape3D.new() cv.points = pts return [cv, Transform3D()] static func is_build(def: Dictionary) -> bool: return def.has("place") and def.place.has("kind") func _make_collision(_mi: MeshInstance3D) -> void: kind = def.place.kind collision_layer = 1 | BUILD_LAYER add_to_group("build") for s in kinds()[kind].shapes: var made := make_shape(s) var cs := CollisionShape3D.new() cs.shape = made[0] cs.transform = made[1] add_child(cs) func snap_points() -> Array: var out := [] for p in kinds()[kind].snaps: out.append(global_transform * p) return out func display_name() -> String: return ItemDB.item_name(item_id) func interact_prompt(_player: Node) -> String: if support < 0.999: return "%s (support %d%%)" % [display_name(), roundi(support * 100.0)] return display_name()