extends Node3D ## Woolies carpark, built in code rather than as a level.glb -- the parked ## shitboxes have to stay shuntable RigidBodies, which a static GLB can't carry. ## Everything else uses the same FLUX textures as the OSM levels. ## ## The Trolley Run junction (junctions.json) runs convoys along the aisles at ## z = 15/12 and z = -21/-24, from x = -70 to 70. Keep those lanes clear. const TEX := "res://assets/textures/%s.jpg" const BAY_ROWS := [-30.0, -12.0, 6.0, 24.0] const BAY_X0 := -27.0 const BAY_STEP := 6.0 const BAYS := 10 func _ready() -> void: var rng := RandomNumberGenerator.new() rng.seed = 8 _slab(Vector3(0, -0.5, 0), Vector3(300, 1, 240), _mat("asphalt", 10.0)) # tarmac for side in [-1, 1]: # kerb walls _slab(Vector3(0, 0.4, side * 79.0), Vector3(240, 0.8, 2), _mat("concrete_ground", 3.0)) _slab(Vector3(side * 119.0, 0.4, 0), Vector3(2, 0.8, 160), _mat("concrete_ground", 3.0)) _bay_lines() _store() # park the actual fleet -- coloured boxes read as placeholder next to # textured everything-else, and we already ship 29 shitbox models var models: Array[String] = [] for id in Registry.cars(): var st: CarStats = load(Registry.cars()[id]) if st.model_path != "" and ResourceLoader.exists(st.model_path): models.append(st.model_path) models.sort() # deterministic for row in BAY_ROWS.size(): for bay in BAYS: if rng.randf() < 0.35: continue var pos := Vector3(BAY_X0 + bay * BAY_STEP, 0.75, BAY_ROWS[row]) _car(pos, models, rng) for i in 6: _light_pole(Vector3(-30.0 + i * 12.0, 0, 8.0)) for x in [-96.0, 96.0]: # planted islands for j in 5: _planter_island(Vector3(x, 0, -60.0 + j * 30.0), rng) for p in [Vector3(-52, 0, 44), Vector3(52, 0, 44), Vector3(-52, 0, -46)]: _trolley_bay(p) _race_path() 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) -> StandardMaterial3D: var m := StandardMaterial3D.new() m.albedo_color = color m.roughness = rough m.metallic = 0.0 return m func _slab(pos: Vector3, size: Vector3, mat: Material, collide := true) -> void: var body: PhysicsBody3D = 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 _car(pos: Vector3, models: Array[String], rng: RandomNumberGenerator) -> void: var body := RigidBody3D.new() body.mass = 300.0 var shape := CollisionShape3D.new() var bs := BoxShape3D.new() bs.size = Vector3(1.9, 1.3, 4.4) shape.shape = bs body.add_child(shape) if models.is_empty(): var mi := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = bs.size bm.material = _flat(Color.from_hsv(rng.randf(), 0.5, 0.7), 0.4) mi.mesh = bm body.add_child(mi) else: # model origin already sits at the 0.75 m ride height, same as the body's body.add_child((load(models[rng.randi() % models.size()]) as PackedScene).instantiate()) body.position = pos body.rotate_y(rng.randf_range(-0.05, 0.05) + (PI if rng.randf() < 0.5 else 0.0)) add_child(body) func _bay_lines() -> void: # painted bay dividers -- visual only, a 12 cm kerb of paint would be absurd var paint := _flat(Color(0.86, 0.86, 0.80), 0.7) for row in BAY_ROWS: for bay in BAYS + 1: var x := BAY_X0 - BAY_STEP * 0.5 + bay * BAY_STEP _slab(Vector3(x, 0.02, row), Vector3(0.14, 0.04, 5.0), paint, false) _slab(Vector3(BAY_X0 + BAY_STEP * (BAYS - 1) * 0.5, 0.02, row - 2.6), Vector3(BAY_STEP * BAYS, 0.04, 0.14), paint, false) func _store() -> void: # the Woolies box at the far end -- what you're aiming at from the spawn var front := -84.0 var mat_wall := _mat("facade_concrete", 0.09, 0.85) var mat_shop := _mat("shopfront", 0.16, 0.3) var mat_sign := _flat(Color(0.06, 0.32, 0.16), 0.6) # supermarket green _slab(Vector3(0, 7.0, front - 14.0), Vector3(130, 14, 28), mat_wall) _slab(Vector3(0, 2.1, front + 0.2), Vector3(120, 4.2, 0.6), mat_shop, false) _slab(Vector3(0, 5.6, front + 0.3), Vector3(120, 2.4, 0.8), mat_sign, false) _slab(Vector3(0, 4.3, front + 2.4), Vector3(126, 0.35, 5.0), _mat("panel_white", 0.4, 0.5), false) # entry canopy for x in [-58.0, -29.0, 0.0, 29.0, 58.0]: _slab(Vector3(x, 2.15, front + 4.7), Vector3(0.3, 4.3, 0.3), _flat(Color(0.2, 0.2, 0.22), 0.5), false) func _light_pole(base: Vector3) -> void: var steel := _flat(Color(0.22, 0.23, 0.25), 0.5) var pole := MeshInstance3D.new() var cm := CylinderMesh.new() cm.top_radius = 0.12 cm.bottom_radius = 0.17 cm.height = 9.0 cm.radial_segments = 8 cm.material = steel pole.mesh = cm pole.position = base + Vector3(0, 4.5, 0) add_child(pole) for s in [-1.0, 1.0]: _slab(base + Vector3(s * 1.4, 8.9, 0), Vector3(2.0, 0.18, 0.6), steel, false) _slab(base + Vector3(s * 2.2, 8.7, 0), Vector3(1.1, 0.22, 0.5), _flat(Color(0.85, 0.84, 0.72), 0.4), false) func _planter_island(pos: Vector3, rng: RandomNumberGenerator) -> void: _slab(pos + Vector3(0, 0.25, 0), Vector3(5.0, 0.5, 11.0), _mat("planter", 0.6, 0.75)) _slab(pos + Vector3(0, 0.52, 0), Vector3(4.4, 0.06, 10.4), _mat("grass", 1.2), false) for j in 2: var at := pos + Vector3(0, 0, -2.6 + j * 5.2) var trunk := MeshInstance3D.new() var cm := CylinderMesh.new() cm.top_radius = 0.13 cm.bottom_radius = 0.2 cm.height = 4.4 cm.radial_segments = 7 cm.material = _mat("bark", 1.4) trunk.mesh = cm trunk.position = at + Vector3(0, 2.7, 0) add_child(trunk) var crown := MeshInstance3D.new() var sm := SphereMesh.new() sm.radius = rng.randf_range(1.9, 2.5) sm.height = sm.radius * 1.7 sm.radial_segments = 12 sm.rings = 7 sm.material = _mat("foliage", 0.55) crown.mesh = sm crown.position = at + Vector3(0, 5.6, 0) add_child(crown) func _trolley_bay(pos: Vector3) -> void: var steel := _flat(Color(0.55, 0.56, 0.58), 0.45) _slab(pos + Vector3(0, 2.5, 0), Vector3(3.0, 0.12, 9.0), _mat("panel_white", 0.5, 0.5), false) for sx in [-1.3, 1.3]: for sz in [-4.0, 4.0]: _slab(pos + Vector3(sx, 1.25, sz), Vector3(0.16, 2.5, 0.16), steel, false) for j in 5: # the trolleys themselves _slab(pos + Vector3(0, 0.5, -3.2 + j * 1.6), Vector3(0.9, 1.0, 1.2), steel, false) func _race_path() -> void: # perimeter loop road around the parking rows -- race line + traffic route var path := Path3D.new() path.name = "RacePath" var c := Curve3D.new() var pts := [Vector3(45, 0, 55), Vector3(45, 0, -55), Vector3(-45, 0, -55), Vector3(-45, 0, 55)] for i in pts.size() + 1: var p: Vector3 = pts[i % pts.size()] var to_prev: Vector3 = (pts[(i - 1 + pts.size()) % pts.size()] - p).normalized() * 12.0 var to_next: Vector3 = (pts[(i + 1) % pts.size()] - p).normalized() * 12.0 c.add_point(p, to_prev, to_next) path.curve = c add_child(path)