- Seeded weather (clear/cloudy/rain/storm, snow and blizzards on cold days and above the snowline), lightning, synthesised rain/wind/thunder sound - Rain/snow particles stopped by roofs; wet ground, settled snow and wind via global shader uniforms - Snap-together wood floor, wall, door, roof and gable with structural support, collapse and X to dismantle - Shelter detection: dry under a roof, warmer and Rested inside with a fire; rain burns an uncovered campfire faster - Bed sets the spawn point and lets you sleep through the night - Trees, boulders and ground pickups regrow; timers saved - Grass cleared under floors; shorter HUD hint - New test_weather_building suite (62 checks) and tests/run_all.sh, which also fails on script errors
136 lines
5.1 KiB
GDScript
136 lines
5.1 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),
|
|
}
|
|
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()
|