extends TestLevelBuilder func _build_environment() -> void: # Dust 2 uses the warm sunset palette. Selecting it through the shared # environment updates both the sky and the fullscreen ink pass. LevelEnvironment.add_to(self, "sunset") func _build_geometry() -> void: # Build environment setup _build_environment() var env = get_node_or_null("WorldEnvironment") if env: var environment = env.environment # (keep the shared linear tonemap — ACES crushes the cel bands) if environment.sky and environment.sky.sky_material: var sky_mat = environment.sky.sky_material sky_mat.set_shader_parameter("top_color", Color(0.3, 0.5, 0.8)) sky_mat.set_shader_parameter("horizon_color", Color(0.8, 0.7, 0.5)) sky_mat.set_shader_parameter("ground_color", Color(0.2, 0.15, 0.1)) _build_lighting() var sun = get_node_or_null("Sun") if sun: sun.light_color = Color(1.0, 0.95, 0.85) sun.light_energy = 1.5 sun.rotation_degrees = Vector3(-45, 45, 0) var fill = get_node_or_null("FillLight") if fill: fill.rotation_degrees = Vector3(-45, -135, 0) fill.light_energy = 0.3 _build_dust2_layout() func _spawn_player(pid: int) -> CharacterBody3D: var player = super._spawn_player(pid) player.position = Vector3(randf_range(-2, 2), 1, 50 + randf_range(-2, 2)) # Looking at (0,1,0) from (0,1,50) is facing straight down the -Z axis, which is rotation = 0,0,0 player.rotation = Vector3.ZERO return player func _build_dust2_layout() -> void: # Materials var wall_mat = LevelMaterials.tinted(Color(0.85, 0.75, 0.6)) # Sandstone var floor_mat = LevelMaterials.tinted(Color(0.7, 0.65, 0.55)) # Dusty ground var box_mat = LevelMaterials.tinted(Color(0.4, 0.3, 0.2), true) # Wood crates # Root CSG — sandstone: the whole map reads acoustically as brick var root_csg = CSGCombiner3D.new() root_csg.use_collision = true root_csg.set_meta("acoustic_material", "brick") add_child(root_csg) # Huge solid block representing the world bounds var world_block = CSGBox3D.new() world_block.size = Vector3(140, 20, 140) world_block.position = Vector3(0, 10, 0) world_block.material = wall_mat root_csg.add_child(world_block) # Helper function to carve out open-air areas (cuts through the ceiling) var carve_open = func(pos: Vector3, size: Vector3): var hole = CSGBox3D.new() hole.operation = CSGShape3D.OPERATION_SUBTRACTION var height = 25.0 - pos.y hole.size = Vector3(size.x, height, size.z) hole.position = Vector3(pos.x, pos.y + (height / 2.0), pos.z) root_csg.add_child(hole) return hole # Helper function to carve out tunnels (leaves a ceiling) var carve_tunnel = func(pos: Vector3, size: Vector3): var hole = CSGBox3D.new() hole.operation = CSGShape3D.OPERATION_SUBTRACTION hole.size = size hole.position = pos + Vector3(0, size.y / 2.0, 0) root_csg.add_child(hole) return hole # Helper function to carve a precise slope (no lips or hard stops) var carve_ramp = func(start_pos: Vector3, width: float, length: float, height: float, is_x_axis: bool, reverse: bool): var ramp_box = CSGBox3D.new() ramp_box.operation = CSGShape3D.OPERATION_SUBTRACTION var slope = atan2(height, length) var dir = 1.0 if reverse else -1.0 if not is_x_axis: # Ramp goes along Z ramp_box.size = Vector3(width, 50, 50) ramp_box.rotation.x = slope * dir var mid_point = start_pos + Vector3(0, height / 2.0, (length / 2.0) * dir) ramp_box.position = mid_point + (Vector3.UP.rotated(Vector3.RIGHT, ramp_box.rotation.x) * 25.0) else: # Ramp goes along X ramp_box.size = Vector3(50, 50, width) ramp_box.rotation.z = -slope * dir var mid_point = start_pos + Vector3((length / 2.0) * dir, height / 2.0, 0) ramp_box.position = mid_point + (Vector3.UP.rotated(Vector3.FORWARD, ramp_box.rotation.z) * 25.0) root_csg.add_child(ramp_box) return ramp_box # --------------------------------------------------------- # LAYOUT (with generous overlaps to prevent getting stuck) # --------------------------------------------------------- # T Spawn (South) carve_open.call(Vector3(0, 0, 50), Vector3(25, 8, 15)) # Suicide & Mid Corridor carve_open.call(Vector3(0, 0, 42), Vector3(8, 8, 10)) # Connects T spawn to Mid carve_open.call(Vector3(0, 0, 15), Vector3(12, 8, 55)) # Main Mid # Mid Doors (Pinch point) carve_open.call(Vector3(0, 0, -14), Vector3(2, 8, 5)) # CT Spawn carve_open.call(Vector3(0, 0, -30), Vector3(30, 8, 30)) # --------------------------------------------------------- # B SIDE # --------------------------------------------------------- # Lower B Tunnels (Connects from Mid at X=0 to West) carve_tunnel.call(Vector3(-18, 0, 30), Vector3(40, 6, 8)) # Spiral stairs up to Upper B Tunnels (Using a ramp instead of stairs for smoothness) carve_ramp.call(Vector3(-34, 0, 34), 8.0, 15.0, 4.0, false, false) # Ramp from Lower to Upper B # Upper B Tunnels carve_tunnel.call(Vector3(-34, 4, 15), Vector3(8, 6, 30)) # B Site carve_open.call(Vector3(-35, 4, -10), Vector3(25, 8, 25)) # B Window & Door (Connecting B Site to CT Spawn) carve_tunnel.call(Vector3(-15, 4, -10), Vector3(25, 6, 5)) # Door carve_tunnel.call(Vector3(-15, 4, -18), Vector3(20, 4, 4)) # Window # CT Spawn Ramp to B Site carve_ramp.call(Vector3(-15, 0, -15), 10.0, 15.0, 4.0, true, false) # Ramp up from CT to B Door # --------------------------------------------------------- # A SIDE # --------------------------------------------------------- # A Long Doors (Connects T Spawn/Mid to A Long) carve_tunnel.call(Vector3(15, 0, 40), Vector3(25, 6, 8)) carve_tunnel.call(Vector3(30, 0, 40), Vector3(8, 6, 15)) # A Long carve_open.call(Vector3(30, 0, 15), Vector3(15, 8, 45)) # A Pit (A hole downwards at the end of A Long) var pit = CSGBox3D.new() pit.operation = CSGShape3D.OPERATION_SUBTRACTION pit.size = Vector3(15, 5, 12) pit.position = Vector3(30, -2.5, 35) root_csg.add_child(pit) # A Site carve_open.call(Vector3(30, 4, -15), Vector3(30, 8, 30)) # Ramp up to A site from Long carve_ramp.call(Vector3(30, 0, -5), 15.0, 15.0, 4.0, false, false) # Ramp down from A Site to CT Spawn carve_ramp.call(Vector3(15, 0, -25), 15.0, 15.0, 4.0, true, true) # Catwalk / A Short carve_open.call(Vector3(15, 4, 5), Vector3(20, 8, 8)) carve_open.call(Vector3(20, 4, -5), Vector3(8, 8, 15)) # Stairs from Mid to Catwalk (Smooth ramp) carve_ramp.call(Vector3(5, 0, 5), 8.0, 12.0, 4.0, true, true) # Floor var floor_box = CSGBox3D.new() floor_box.size = Vector3(140, 1, 140) floor_box.position = Vector3(0, -0.5, 0) floor_box.material = floor_mat root_csg.add_child(floor_box) # Cover Boxes var add_box = func(pos: Vector3, size: Vector3): var b = CSGBox3D.new() b.size = size b.position = pos + Vector3(0, size.y / 2.0, 0) b.material = box_mat root_csg.add_child(b) add_box.call(Vector3(4, 0, -12), Vector3(2, 2, 2)) add_box.call(Vector3(30, 4, -15), Vector3(4, 2, 4)) add_box.call(Vector3(30, 6, -15), Vector3(2, 2, 2)) add_box.call(Vector3(-35, 4, -10), Vector3(2, 2, 2)) # Dummies _build_dust2_dummies() # Combat Area Bounds # Envelops the playable space; centred so it fits around the CSG boundaries. # Deeper than the old 20 m box, which sat with its floor at y = 0 and so # never actually contained a player who had fallen below the map. CombatArea.add_to(self, Vector3(120, 70, 120), Vector3(0, 25, 0)) func _build_dust2_dummies() -> void: # Do not spawn dummies in real multiplayer matches var nm = get_node_or_null("/root/NetworkManager") if nm and nm.multiplayer.multiplayer_peer is ENetMultiplayerPeer: return var spawn_dummy = func(pos: Vector3, rot_y: float): var dummy = load("res://entities/killable_dummy.gd").new() dummy.position = pos dummy.rotation_degrees.y = rot_y add_child(dummy) # Mid Doors CT side looking out spawn_dummy.call(Vector3(0, 0, -20), 0) # A Long Corner looking towards doors spawn_dummy.call(Vector3(34, 0, 10), 180) # B Site Car spawn_dummy.call(Vector3(-30, 4, -5), 90) # A Site Goose spawn_dummy.call(Vector3(40, 4, -25), -135)