extends Node3D ## THE SHED -- home base. A suburban backyard on a quiet cul-de-sac: brick ## house, paling fence, hills hoist mid-spin, and the corrugated double shed ## on its slab. The tools you actually OWN (Garage.tools()) stand in it as the ## real TRELLIS'd cashies models; unowned tools are an oil-stained outline of ## where they'll go. Roll into the marked bay and stop -- game.gd opens the ## garage. Cruise mode only ever spawns here quietly: no RacePath, no traffic, ## just you, the lawn, and the neighbours' opinion of your exhaust. const TEX := "res://assets/textures/%s.jpg" ## shed-catalogue tool id -> {model, pos, yaw}. Bench-top items carry y. const TOOL_SPOTS := { "ramps": {"model": "car_ramps", "pos": Vector3(10.5, 0.07, -9.6), "yaw": 0.1}, "reader": {"model": "obd_reader", "pos": Vector3(9.0, 0.99, -19.8), "yaw": 0.4}, "sockets": {"model": "socket_set", "pos": Vector3(11.5, 0.99, -19.8), "yaw": -0.2}, "compressor": {"model": "compressor", "pos": Vector3(8.2, 0.07, -17.5), "yaw": 1.2}, "hoist": {"model": "engine_crane", "pos": Vector3(15.2, 0.07, -17.0), "yaw": -0.5}, "additives": {"model": "jerry_can", "pos": Vector3(8.0, 0.07, -13.5), "yaw": 2.1}, } ## always-there garnish: the rest of the library, scattered like a real shed const CLUTTER := [ ["toolbox", Vector3(16.1, 0.07, -11.5), 1.9], ["trolley_jack", Vector3(11.0, 0.07, -15.5), 0.3], ["jack_stands", Vector3(15.8, 0.07, -9.8), 2.6], ["welder", Vector3(7.8, 0.07, -15.8), 1.6], ["batt_charger", Vector3(14.0, 0.99, -19.8), -0.3], ["angle_grinder", Vector3(16.0, 0.99, -19.7), 0.9], ["grease_gun", Vector3(13.0, 0.07, -16.5), 4.1], ["chain_hoist", Vector3(7.4, 0.07, -19.3), 0.6], ["engine_stand", Vector3(19.8, 0.0, -12.0), 5.4], ] var hoist: Node3D func _ready() -> void: var rng := RandomNumberGenerator.new() rng.seed = 4074 # the postcode of paradise _slab(Vector3(-3, -0.5, 5), Vector3(120, 1, 120), _mat("grass", 10.0)) # world _slab(Vector3(0, 0.015, 31), Vector3(88, 0.03, 14), _mat("asphalt", 8.0), false) # street _slab(Vector3(0, 0.02, 22), Vector3(88, 0.04, 2.2), _mat("footpath", 2.0), false) # footpath _slab(Vector3(12, 0.025, 8), Vector3(8, 0.05, 32), _mat("concrete_ground", 3.0), false) # driveway _fence() _house() _shed() _hills_hoist() _tools() _neighbours() _gums(rng) # wheelie bins by the gate, a spare shitbox on the driveway (game.gd fills) for i in 2: var m := Marker3D.new() m.name = "WB_%d" % i m.position = Vector3(18.8, 0, 17.0 - i * 1.4) add_child(m) # the spare shitbox parks on the street now -- the driveway is for customers var pp := Marker3D.new() pp.name = "PP_kerb" pp.position = Vector3(-8, 0, 30.5) pp.rotate_y(PI * 0.5 + 0.04) add_child(pp) # a customer waits on the driveway (real play only: the wrench scene uses # this level as backdrop and must never summon one mid-job) if get_parent() is Game and Game.mode == "cruise": _customer() func _customer() -> void: var cust := Garage.customer_arrive() if cust.is_empty(): return var cars := Registry.cars() if cars.has(cust["car"]): var st: CarStats = load(cars[cust["car"]]) if st.model_path != "" and ResourceLoader.exists(st.model_path): var m := (load(st.model_path) as PackedScene).instantiate() add_child(m) m.position = Vector3(12, 0.72, 6.5) m.rotate_y(0.12) var spot := Marker3D.new() spot.name = "CustomerSpot" spot.position = Vector3(12, 0, 6.5) add_child(spot) var sign := Label3D.new() sign.text = "%s\n\"%s\"\n%s -- $%d" % [cust["name"], cust["blurb"], Garage.FAULTS[cust["fault"]]["name"], cust["pay"]] sign.font_size = 64 sign.pixel_size = 0.004 sign.billboard = BaseMaterial3D.BILLBOARD_ENABLED sign.modulate = Color(1, 0.85, 0.4) sign.outline_size = 12 sign.position = Vector3(12, 2.7, 6.5) add_child(sign) func _process(delta: float) -> void: if hoist: hoist.rotate_y(delta * 0.35) # there is always a bit of wind ## ---- the yard ------------------------------------------------------------- func _fence() -> void: var pal := _mat("paling_fence", 0.55, 0.9) for seg in [ [Vector3(-30, 0.85, -4), Vector3(0.08, 1.7, 48)], # west [Vector3(24, 0.85, -4), Vector3(0.08, 1.7, 48)], # east [Vector3(-3, 0.85, -28), Vector3(54, 1.7, 0.08)], # back [Vector3(-11.5, 0.85, 20), Vector3(37, 1.7, 0.08)], # front west of gate [Vector3(20.5, 0.85, 20), Vector3(7, 1.7, 0.08)], # front east of gate ]: _slab(seg[0], seg[1], pal) func _house() -> void: # single-storey: brick tiled SPARSE (0.045) or the place reads as an office var brick := _mat("facade_brick", 0.045, 0.85) _slab(Vector3(-12, 1.9, 8), Vector3(24, 3.8, 20), brick) _slab(Vector3(-12, 1.7, 18.1), Vector3(18, 1.2, 0.15), _mat("facade_glass", 0.25, 0.3), false) # corrugated hip-ish roof: two tilted sheets meeting at a ridge for s in [-1.0, 1.0]: var roof := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = Vector3(25.2, 0.14, 11.0) bm.material = _mat("corrugated_iron", 1.3, 0.6) roof.mesh = bm roof.position = Vector3(-12, 4.9, 8 + s * 4.9) roof.rotation_degrees = Vector3(-s * 14.0, 0, 0) add_child(roof) # porch: slab, posts, and a light for coming home at night _slab(Vector3(-6, 0.1, 19.3), Vector3(8, 0.2, 2.6), _mat("concrete_ground", 2.0), false) for px in [-9.5, -2.5]: _slab(Vector3(px, 1.5, 20.2), Vector3(0.14, 3.0, 0.14), _flat(Color(0.85, 0.83, 0.78), 0.7), false) _slab(Vector3(-6, 3.05, 19.9), Vector3(0.3, 0.14, 0.3), _flat(Color(0.9, 0.87, 0.6), 0.4, Color(0.9, 0.8, 0.4)), false) if Game.time_of_day == "night": var ol := OmniLight3D.new() ol.light_color = Color(1.0, 0.85, 0.55) ol.light_energy = 1.4 ol.omni_range = 10.0 ol.position = Vector3(-6, 2.8, 19.6) add_child(ol) func _shed() -> void: ## Double garage, not a warehouse: 10 x 12 m, open front, slab, pegboard. var iron := _mat("corrugated_iron", 1.4, 0.6) _slab(Vector3(12, 0.035, -15), Vector3(10.6, 0.07, 12.6), _mat("concrete_ground", 2.5)) _slab(Vector3(7, 1.7, -15), Vector3(0.1, 3.4, 12.6), iron) # west wall _slab(Vector3(17, 1.7, -15), Vector3(0.1, 3.4, 12.6), iron) # east wall _slab(Vector3(12, 1.85, -21.2), Vector3(10.2, 3.7, 0.1), iron) # back wall _slab(Vector3(12, 2.55, -20.9), Vector3(9.6, 1.9, 0.06), _mat("pegboard", 0.4, 0.9), false) # workbench along the back _slab(Vector3(11.5, 0.45, -19.9), Vector3(8.8, 0.9, 1.2), _flat(Color(0.32, 0.26, 0.2), 0.8)) _slab(Vector3(11.5, 0.93, -19.9), Vector3(9.0, 0.08, 1.35), _flat(Color(0.55, 0.48, 0.38), 0.6), false) # skillion roof, high at the front so the ute fits var roof := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = Vector3(11.4, 0.12, 13.6) bm.material = iron roof.mesh = bm roof.position = Vector3(12, 3.9, -15) roof.rotation_degrees = Vector3(-3.2, 0, 0) add_child(roof) # fluoro batten under the roof -- and a real light at night _slab(Vector3(12, 3.35, -15), Vector3(0.2, 0.08, 4.2), _flat(Color(0.95, 0.96, 0.9), 0.3, Color(0.9, 0.93, 0.85)), false) if Game.time_of_day == "night": var ol := OmniLight3D.new() ol.light_color = Color(0.92, 0.95, 0.88) ol.light_energy = 2.2 ol.omni_range = 14.0 ol.position = Vector3(12, 3.1, -15) add_child(ol) # the bay: painted outline + the marker game.gd watches var paint := _flat(Color(0.86, 0.86, 0.8), 0.7) for sx in [9.5, 14.5]: _slab(Vector3(sx, 0.075, -14.2), Vector3(0.12, 0.02, 6.4), paint, false) _slab(Vector3(12, 0.075, -17.4), Vector3(5.1, 0.02, 0.12), paint, false) var bay := Marker3D.new() bay.name = "GarageBay" bay.position = Vector3(12, 0, -14.2) add_child(bay) func _hills_hoist() -> void: var steel := _flat(Color(0.6, 0.61, 0.63), 0.4) steel.metallic = 0.6 _cyl(Vector3(-14, 1.3, -20), 0.05, 2.6, steel) hoist = Node3D.new() hoist.position = Vector3(-14, 2.55, -20) add_child(hoist) for i in 4: var arm := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = Vector3(3.4, 0.05, 0.05) bm.material = steel arm.mesh = bm arm.rotation_degrees = Vector3(0, 45.0 + i * 90.0, 4.0) hoist.add_child(arm) for i in 2: # wire squares var r := 1.0 + i * 0.55 for j in 4: var wire := MeshInstance3D.new() var wm := BoxMesh.new() wm.size = Vector3(r * 1.41, 0.015, 0.015) wm.material = steel wire.mesh = wm wire.position = Vector3(0, 0.06 + i * 0.02, 0).rotated(Vector3.UP, 0) wire.rotation_degrees = Vector3(0, j * 90.0, 0) wire.position = Vector3(cos(deg_to_rad(j * 90.0 + 90.0)), 0.08, sin(deg_to_rad(j * 90.0 + 90.0))) * r * 0.0 wire.position.y = 0.08 wire.transform.origin += wire.basis.z * r * 0.7 hoist.add_child(wire) # the washing: towels that have seen some things for c in [[Color(0.75, 0.5, 0.45), 0.0], [Color(0.5, 0.6, 0.75), 1.7], [Color(0.85, 0.82, 0.7), 3.6]]: var tow := MeshInstance3D.new() var tm := BoxMesh.new() tm.size = Vector3(0.7, 0.55, 0.02) tm.material = _flat(c[0], 0.9) tow.mesh = tm tow.rotation_degrees = Vector3(0, rad_to_deg(float(c[1])), 0) tow.position = Vector3(cos(float(c[1])), -0.35, sin(float(c[1]))) * 1.2 hoist.add_child(tow) func _tools() -> void: ## Owned catalogue tools materialise as the real models; unowned leave an ## oil-stained promise on the slab. Clutter is permanent, like real clutter. for id in TOOL_SPOTS: var spot: Dictionary = TOOL_SPOTS[id] if Garage.has_tool(id): ToolProps.spawn(self, spot["model"], spot["pos"], float(spot["yaw"]), "shed_tool", id) else: _slab(Vector3(spot["pos"].x, 0.078, spot["pos"].z), Vector3(0.7, 0.01, 0.5), _flat(Color(0.09, 0.08, 0.06), 0.3), false) for c in CLUTTER: ToolProps.spawn(self, c[0], c[1], float(c[2])) func _neighbours() -> void: ## Across the road: enough suburbia to feel watched. for i in 3: var x := -34.0 + i * 30.0 _slab(Vector3(x, 1.8, 47), Vector3(18, 3.6, 14), _mat("facade_brick" if i != 1 else "facade_concrete", 0.045, 0.85)) var roof := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = Vector3(19.5, 0.14, 15.5) bm.material = _mat("corrugated_iron", 1.3, 0.6) roof.mesh = bm roof.position = Vector3(x, 4.2, 47) roof.rotation_degrees = Vector3(6, 0, 0) add_child(roof) _slab(Vector3(x, 0.85, 39.2), Vector3(18, 1.7, 0.08), _mat("paling_fence", 0.55, 0.9)) func _gums(rng: RandomNumberGenerator) -> void: for at in [Vector3(-26, 0, -22), Vector3(20, 0, -25), Vector3(-27, 0, 14), Vector3(-38, 0, 24), Vector3(34, 0, 23)]: var h := rng.randf_range(5.5, 8.5) _cyl(at + Vector3(0, h * 0.5, 0), 0.2, h, _mat("bark", 1.4)) var crown := MeshInstance3D.new() var sm := SphereMesh.new() sm.radius = rng.randf_range(2.4, 3.4) sm.height = sm.radius * 1.6 sm.radial_segments = 10 sm.rings = 6 sm.material = _mat("foliage", 0.55) crown.mesh = sm crown.position = at + Vector3(0, h + sm.radius * 0.35, 0) add_child(crown) ## ---- helpers -------------------------------------------------------------- func _mat(tex: String, scale: float, rough := 0.9) -> StandardMaterial3D: var m := StandardMaterial3D.new() m.albedo_texture = load(TEX % tex) m.uv1_scale = Vector3(scale, scale, scale) m.uv1_triplanar = true m.roughness = rough m.metallic = 0.0 return m func _flat(color: Color, rough := 0.85, emit := Color.BLACK) -> StandardMaterial3D: var m := StandardMaterial3D.new() m.albedo_color = color m.roughness = rough m.metallic = 0.0 if emit != Color.BLACK: m.emission_enabled = true m.emission = emit m.emission_energy_multiplier = 1.2 return m func _slab(pos: Vector3, size: Vector3, mat: Material, collide := true) -> void: var body := StaticBody3D.new() var mi := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = size bm.material = mat mi.mesh = bm body.add_child(mi) if collide: var shape := CollisionShape3D.new() var bs := BoxShape3D.new() bs.size = size shape.shape = bs body.add_child(shape) body.position = pos add_child(body) func _cyl(pos: Vector3, r: float, h: float, mat: Material) -> void: var mi := MeshInstance3D.new() var cm := CylinderMesh.new() cm.top_radius = r cm.bottom_radius = r * 1.2 cm.height = h cm.radial_segments = 8 cm.material = mat mi.mesh = cm mi.position = pos add_child(mi)