Files
Cinderwild/scripts/building/build_piece.gd

178 lines
7.0 KiB
GDScript

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),
}
# --- 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).
# (The collider stops 0.2 m short of the top: that's where a floor resting
# on it has its slab, and the overlap check would count it as in the way.)
k["pillar"] = {
"shapes": [_box(Vector3(0, 0.9, 0), Vector3(0.4, 1.8, 0.4))],
"snaps": [Vector3(0, 0, 0), Vector3(0, 2, 0)],
"center": Vector3(0, 1, 0),
}
# Stairs: 2 m wide, climbing 2 m over 4 m toward -Z (the roof's slope):
# low edge on a floor's edge (or the ground), top edge at the next floor
# up. One smooth ramp to walk on; the steps are in the mesh.
var sn := Vector3(0, cos(ROOF_ANGLE), sin(ROOF_ANGLE))
k["stairs"] = {
"shapes": [{"type": "box", "center": Vector3(0, 1, -2) - sn * 0.1,
"size": Vector3(2, 0.2, sqrt(20.0)), "basis": Basis(Vector3.RIGHT, ROOF_ANGLE)}],
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0),
Vector3(-1, 2, -4), Vector3(0, 2, -4), Vector3(1, 2, -4)],
"center": Vector3(0, 1, -2),
}
# Window: a wall with a 1 x 0.7 m opening, 1 m up.
k["window"] = {
"shapes": [_box(Vector3(0, 0.5, 0), Vector3(2, 1, 0.2)), _box(Vector3(0, 1.85, 0), Vector3(2, 0.3, 0.2)),
_box(Vector3(-0.75, 1.35, 0), Vector3(0.5, 0.7, 0.2)), _box(Vector3(0.75, 1.35, 0), Vector3(0.5, 0.7, 0.2))],
"snaps": wall_snaps,
"center": Vector3(0, 1, 0),
}
# Palisade: 2 m of sharpened logs, 3 m tall, for a ring round your camp.
k["palisade"] = {
"shapes": [_box(Vector3(0, 1.5, 0), Vector3(2, 3, 0.36))],
"snaps": [Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(1, 0, 0)],
"center": Vector3(0, 1.5, 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")
## "wood" or "stone" (items.json place.material). Stone loses less support
## going up and can't stand on wood (build_system.gd).
var material := "wood"
static func material_of(d: Dictionary) -> String:
return String(d.get("place", {}).get("material", "wood"))
func _make_collision(_mi: MeshInstance3D) -> void:
kind = def.place.kind
material = material_of(def)
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()