restore test level and dust2, fix dust2 builder to not use TestLevelBuilder

This commit is contained in:
2026-06-25 17:27:47 -04:00
parent 6b82258cec
commit a351cf2dc4
8 changed files with 385 additions and 1 deletions
+241
View File
@@ -0,0 +1,241 @@
extends Node3D
func _ready() -> void:
_build()
func _build() -> void:
_build_environment()
_build_dust2_layout()
func _build_environment() -> void:
var env = WorldEnvironment.new()
env.name = "WorldEnvironment"
var environment = Environment.new()
environment.background_mode = Environment.BG_SKY
var sky = Sky.new()
var sky_mat = ProceduralSkyMaterial.new()
sky_mat.sky_top_color = Color(0.3, 0.5, 0.8)
sky_mat.sky_horizon_color = Color(0.8, 0.7, 0.5)
sky_mat.ground_bottom_color = Color(0.2, 0.15, 0.1)
sky_mat.ground_horizon_color = Color(0.8, 0.7, 0.5)
sky.sky_material = sky_mat
environment.sky = sky
environment.tonemap_mode = Environment.TONE_MAPPER_ACES
environment.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
environment.ambient_light_energy = 0.35
env.environment = environment
add_child(env)
var sun = DirectionalLight3D.new()
sun.name = "Sun"
sun.rotation_degrees = Vector3(-45, 45, 0)
sun.light_color = Color(1.0, 0.95, 0.85)
sun.light_energy = 1.5
sun.shadow_enabled = true
add_child(sun)
var fill = DirectionalLight3D.new()
fill.name = "FillLight"
fill.rotation_degrees = Vector3(-45, -135, 0)
fill.light_energy = 0.3
add_child(fill)
func _spawn_player(pid: int) -> CharacterBody3D:
var player = CharacterBody3D.new()
player.name = str(pid)
player.position = Vector3(randf_range(-2, 2), 1, 50 + randf_range(-2, 2))
player.rotation = Vector3.ZERO
return player
func _build_dust2_layout() -> void:
# Materials
var wall_mat = StandardMaterial3D.new()
wall_mat.albedo_color = Color(0.85, 0.75, 0.6) # Sandstone
var floor_mat = StandardMaterial3D.new()
floor_mat.albedo_color = Color(0.7, 0.65, 0.55) # Dusty ground
var box_mat = StandardMaterial3D.new()
box_mat.albedo_color = Color(0.4, 0.3, 0.2) # Wood crates
# Root CSG
var root_csg = CSGCombiner3D.new()
root_csg.use_collision = true
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
var combat_area = load("res://entities/combat_area.gd").new()
combat_area.name = "CombatArea"
var area_shape = CollisionShape3D.new()
var cbox = BoxShape3D.new()
# Size the combat area to envelop the entire playable space (width and depth of 120, height of 20)
cbox.size = Vector3(120, 20, 120)
area_shape.shape = cbox
combat_area.add_child(area_shape)
# Center it on the map so it fits perfectly around the CSG boundaries
combat_area.position = Vector3(0, 10, 0)
add_child(combat_area)
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)
+115
View File
@@ -0,0 +1,115 @@
extends Node3D
## Builds the test level ONLY from code — no .tscn SubResource references.
## One-click playtest: attach to TestLevel root, F5.
func _ready() -> void:
build()
func build() -> void:
_build_floor()
_build_walls()
_build_lighting()
_build_player()
_build_camera()
_build_ui()
func _box_static(pos: Vector3, size: Vector3, color: Color) -> StaticBody3D:
var body := StaticBody3D.new()
body.name = "Static_" + str(pos)
body.position = pos
add_child(body)
var shape := CollisionShape3D.new()
shape.shape = BoxShape3D.new()
shape.shape.size = size
body.add_child(shape)
var mesh := MeshInstance3D.new()
mesh.mesh = BoxMesh.new()
mesh.mesh.size = size
var mat := StandardMaterial3D.new()
mat.albedo_color = color
mesh.mesh.surface_set_material(0, mat)
body.add_child(mesh)
return body
func _build_floor() -> void:
_box_static(Vector3(0, -1.1, 0), Vector3(30, 0.4, 30), Color(0.35, 0.25, 0.15))
func _build_walls() -> void:
_box_static(Vector3(0, 3, -15), Vector3(30, 6, 0.4), Color(0.55, 0.45, 0.35))
_box_static(Vector3(15, 3, 0), Vector3(0.4, 6, 30), Color(0.55, 0.45, 0.35))
_box_static(Vector3(-15, 3, 0), Vector3(0.4, 6, 30), Color(0.55, 0.45, 0.35))
func _build_lighting() -> void:
var sun := DirectionalLight3D.new()
sun.name = "Sun"
sun.position = Vector3(0, 10, 0)
sun.rotation_degrees = Vector3(50, 0, 0)
sun.light_color = Color(1, 0.95, 0.85)
sun.light_energy = 1.1
sun.shadow_enabled = true
add_child(sun)
func _build_player() -> void:
var player := CharacterBody3D.new()
player.name = "Player"
player.position = Vector3(0, 1.5, 8)
add_child(player)
var shape := CollisionShape3D.new()
shape.shape = CapsuleShape3D.new()
shape.shape.radius = 0.4
shape.shape.height = 1.8
player.add_child(shape)
var sm := Node.new()
sm.name = "MovementStateMachine"
player.add_child(sm)
for state_name in ["ground", "air", "wall_run", "wall_cling", "slide", "dash"]:
var st := Node.new()
st.name = "state_" + state_name
sm.add_child(st)
var mover_script = load("res://movement/player_movement_controller.gd")
if mover_script:
player.set_script(mover_script)
# set_script after node is in tree does not auto-call _ready()
# Manually initialize the controller
if player.has_method("_ready"):
player._ready()
func _build_camera() -> void:
var cam := Camera3D.new()
cam.name = "FpsCamera"
cam.current = true
cam.position = Vector3(0, 1.7, 0)
cam.fov = 90.0
var player_node = get_node_or_null("Player")
if player_node:
player_node.add_child(cam)
else:
add_child(cam)
func _build_ui() -> void:
var canvas := CanvasLayer.new()
canvas.name = "UI"
add_child(canvas)
var speed := Label.new()
speed.name = "SpeedLabel"
speed.offset_left = 16
speed.offset_top = 16
speed.offset_right = 520
speed.offset_bottom = 48
speed.text = "Speed: 0.0 m/s | State: --- | Chain: +0%"
canvas.add_child(speed)
var help := Label.new()
help.name = "HelpLabel"
help.offset_left = 16
help.offset_top = 52
help.offset_right = 720
help.offset_bottom = 108
help.text = "WASD Move | Sprint=Shift | Slide=Ctrl+S | Double-Jump=Space twice | Dash=LShift | Wall-Jump=Jump at wall"
canvas.add_child(help)