Feat/14 movement overhaul #20

Merged
Dotts merged 86 commits from feat/14-movement-overhaul into main 2026-07-17 10:44:23 -07:00
Showing only changes of commit 671b36c20c - Show all commits
+100 -448
View File
@@ -21,7 +21,6 @@ var _debug_ui_panel: PanelContainer
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
_load_props()
_build_materials()
_build_geometry()
_build_hud()
@@ -52,26 +51,18 @@ func _build_materials() -> void:
# ═══════════════════════════════════════════════════════════════════════════════
# GEOMETRY
# GEOMETRY — Clean 80x80 arena
# ═══════════════════════════════════════════════════════════════════════════════
func _build_geometry() -> void:
_build_environment()
_build_floor()
_build_outer_walls()
_build_central_structure()
_build_corner_towers()
_build_elevated_walkways()
_build_central_platform()
_build_ramps()
_build_stepping_stones()
_build_wall_run_surfaces()
_build_wall_climb_surfaces()
_build_grapple_anchors()
_build_cover_system()
_build_speed_corridor()
_build_cover()
_build_spawn_alcoves()
_build_decorations()
_build_target_dummy()
_build_dummies()
_build_lighting()
@@ -103,63 +94,21 @@ func _ramp_static(pos: Vector3, size: Vector3, rot_deg: Vector3, color: Color, n
return body
# ── GLB Prop Scenes (generated via Blender headless) ──────────────────────────
var prop_crate: PackedScene
var prop_barrel: PackedScene
var prop_pillar: PackedScene
var prop_ramp: PackedScene
var prop_weapon_pickup: PackedScene
var prop_health_pack: PackedScene
var prop_ammo_pack: PackedScene
func _load_props() -> void:
prop_crate = load("res://assets/props/crate.glb")
prop_barrel = load("res://assets/props/barrel.glb")
prop_pillar = load("res://assets/props/pillar.glb")
prop_ramp = load("res://assets/props/ramp.glb")
prop_weapon_pickup = load("res://assets/props/weapon_pickup.glb")
prop_health_pack = load("res://assets/props/health_pack.glb")
prop_ammo_pack = load("res://assets/props/ammo_pack.glb")
# Log which props loaded
if prop_crate: print("Prop loaded: crate")
else: print("WARN: crate.glb not imported yet - run Godot editor once to import")
if prop_barrel: print("Prop loaded: barrel")
if prop_pillar: print("Prop loaded: pillar")
func _place_prop(scene: PackedScene, pos: Vector3, name: String = "") -> void:
if not scene:
return # Skip if not imported yet
var inst = scene.instantiate()
if inst:
inst.name = name if not name.is_empty() else scene.resource_path.get_file().get_basename()
inst.position = pos
add_child(inst)
# ── Color Palette ─────────────────────────────────────────────────────────────
var C_FLOOR: Color = Color(0.22, 0.22, 0.25)
var C_FLOOR_ACCENT: Color = Color(0.28, 0.26, 0.22)
var C_WALL: Color = Color(0.35, 0.28, 0.22)
var C_WALL_DARK: Color = Color(0.25, 0.20, 0.18)
var C_METAL: Color = Color(0.50, 0.52, 0.55)
var C_METAL_DARK: Color = Color(0.35, 0.37, 0.40)
var C_TEAL: Color = Color(0.20, 0.45, 0.55)
var C_PURPLE: Color = Color(0.45, 0.25, 0.55)
var C_ORANGE: Color = Color(0.60, 0.35, 0.15)
var C_WOOD: Color = Color(0.55, 0.38, 0.22)
var C_CRATE: Color = Color(0.65, 0.50, 0.30)
var C_RED: Color = Color(0.70, 0.22, 0.22)
var C_BLUE: Color = Color(0.22, 0.30, 0.70)
var C_ACCENT_WARM: Color = Color(0.85, 0.55, 0.20)
var C_ACCENT_COOL: Color = Color(0.20, 0.50, 0.85)
var C_WALLRUN: Color = Color(0.20, 0.45, 0.65)
var C_WALLCLIMB: Color = Color(0.55, 0.45, 0.20)
var C_GREEN: Color = Color(0.20, 0.35, 0.20)
var C_DARK_GREEN: Color = Color(0.15, 0.25, 0.15)
var C_YELLOW: Color = Color(0.80, 0.75, 0.20)
var C_RAMP: Color = Color(0.60, 0.35, 0.15)
var C_FLOOR := Color(0.22, 0.22, 0.25)
var C_WALL := Color(0.35, 0.28, 0.22)
var C_WALL_DARK := Color(0.25, 0.20, 0.18)
var C_METAL := Color(0.50, 0.52, 0.55)
var C_METAL_DARK := Color(0.35, 0.37, 0.40)
var C_TEAL := Color(0.20, 0.45, 0.55)
var C_CRATE := Color(0.65, 0.50, 0.30)
var C_RED := Color(0.70, 0.22, 0.22)
var C_BLUE := Color(0.22, 0.30, 0.70)
var C_ACCENT_WARM := Color(0.85, 0.55, 0.20)
var C_ACCENT_COOL := Color(0.20, 0.50, 0.85)
var C_RAMP := Color(0.60, 0.35, 0.15)
var C_YELLOW := Color(0.80, 0.75, 0.20)
# ── Environment ───────────────────────────────────────────────────────────────
@@ -193,340 +142,107 @@ func _build_environment() -> void:
# ── Floor ─────────────────────────────────────────────────────────────────────
func _build_floor() -> void:
# Main arena floor (96x96)
_box_static(Vector3(0, -0.5, 0), Vector3(96, 1.0, 96), C_FLOOR, "Floor_Main")
# Floor accent tiles (checkerboard pattern in central area)
for ix in range(-4, 5):
for iz in range(-4, 5):
if (ix + iz) % 2 == 0:
_box_static(
Vector3(float(ix) * 4.0, -0.49, float(iz) * 4.0),
Vector3(3.8, 0.02, 3.8),
C_FLOOR_ACCENT,
"Floor_Tile_%d_%d" % [ix, iz]
)
# Main arena floor (80x80)
_box_static(Vector3(0, -0.5, 0), Vector3(80, 1.0, 80), C_FLOOR, "Floor_Main")
# ── Outer Walls ───────────────────────────────────────────────────────────────
func _build_outer_walls() -> void:
var wall_height := 12.0
var map_half := 48.0
# North
_box_static(Vector3(0, wall_height * 0.5, -map_half), Vector3(96, wall_height, 1.0), C_WALL, "Wall_North")
# South
_box_static(Vector3(0, wall_height * 0.5, map_half), Vector3(96, wall_height, 1.0), C_WALL, "Wall_South")
# East
_box_static(Vector3(map_half, wall_height * 0.5, 0), Vector3(1.0, wall_height, 96), C_WALL, "Wall_East")
# West
_box_static(Vector3(-map_half, wall_height * 0.5, 0), Vector3(1.0, wall_height, 96), C_WALL, "Wall_West")
var wall_height := 10.0
var half := 40.0
_box_static(Vector3(0, wall_height / 2.0, -half), Vector3(80, wall_height, 1.0), C_WALL, "Wall_North")
_box_static(Vector3(0, wall_height / 2.0, half), Vector3(80, wall_height, 1.0), C_WALL, "Wall_South")
_box_static(Vector3(half, wall_height / 2.0, 0), Vector3(1.0, wall_height, 80), C_WALL, "Wall_East")
_box_static(Vector3(-half, wall_height / 2.0, 0), Vector3(1.0, wall_height, 80), C_WALL, "Wall_West")
# ── Central Structure ─────────────────────────────────────────────────────────
# ── Central Platform ──────────────────────────────────────────────────────────
func _build_central_structure() -> void:
# Main platform (3u high, 20x20)
_box_static(Vector3(0, 1.5, 0), Vector3(20, 3.0, 20), C_METAL_DARK, "Central_Platform")
# Upper tower (6u high, 10x10)
_box_static(Vector3(0, 6.0, 0), Vector3(10, 6.0, 10), C_WALL_DARK, "Central_Tower")
# Tower top platform (9u high, 14x14) - grapple target
_box_static(Vector3(0, 11.5, 0), Vector3(14, 1.0, 14), C_METAL, "Central_Top")
# Tower crown walls (partial cover on top)
_box_static(Vector3(-5, 13.5, -5), Vector3(4, 3.0, 0.5), C_METAL, "Crown_NW")
_box_static(Vector3(5, 13.5, 5), Vector3(4, 3.0, 0.5), C_METAL, "Crown_SE")
_box_static(Vector3(-5, 13.5, 5), Vector3(0.5, 3.0, 4), C_METAL, "Crown_SW")
_box_static(Vector3(5, 13.5, -5), Vector3(0.5, 3.0, 4), C_METAL, "Crown_NE")
# Staircase inside tower (ramp approximation with steps)
for i in range(8):
var step_y := 2.0 + float(i) * 0.85
var step_size := 6.0 - float(i) * 0.4
_box_static(
Vector3(0, step_y, -3.0 + float(i) * 0.3),
Vector3(step_size, 0.3, 2.5),
C_METAL,
"Central_Step_%d" % i
)
# ── Corner Towers ─────────────────────────────────────────────────────────────
func _build_corner_towers() -> void:
# Each corner has a tower (5u high, 8x8 base) with spawn alcoves at base
var tower_offsets := [
Vector3(-36, 0, -36),
Vector3(36, 0, -36),
Vector3(-36, 0, 36),
Vector3(36, 0, 36),
]
for offset in tower_offsets:
# Tower base
_box_static(offset + Vector3(0, 2.5, 0), Vector3(8, 5.0, 8), C_WALL_DARK, "Tower_%d" % tower_offsets.find(offset))
# Tower top platform
_box_static(offset + Vector3(0, 5.5, 0), Vector3(10, 1.0, 10), C_METAL, "TowerTop_%d" % tower_offsets.find(offset))
# Tower corner railing
_box_static(offset + Vector3(4, 7.0, 4), Vector3(2, 2.0, 0.4), C_METAL, "TowerRailA_%d" % tower_offsets.find(offset))
_box_static(offset + Vector3(4, 7.0, -4), Vector3(0.4, 2.0, 2), C_METAL, "TowerRailB_%d" % tower_offsets.find(offset))
# ── Elevated Walkways ─────────────────────────────────────────────────────────
func _build_elevated_walkways() -> void:
# Mid-side elevated walkways (3u high) connecting central to edges
# North walkway
_box_static(Vector3(0, 3.0, -30), Vector3(6, 0.6, 20), C_METAL, "Walkway_North")
# South walkway
_box_static(Vector3(0, 3.0, 30), Vector3(6, 0.6, 20), C_METAL, "Walkway_South")
# East walkway
_box_static(Vector3(30, 3.0, 0), Vector3(20, 0.6, 6), C_METAL, "Walkway_East")
# West walkway
_box_static(Vector3(-30, 3.0, 0), Vector3(20, 0.6, 6), C_METAL, "Walkway_West")
# Walkway railings
_box_static(Vector3(-3.2, 4.5, -30), Vector3(0.3, 2.0, 20), C_METAL, "Rail_N_W")
_box_static(Vector3(3.2, 4.5, -30), Vector3(0.3, 2.0, 20), C_METAL, "Rail_N_E")
_box_static(Vector3(-3.2, 4.5, 30), Vector3(0.3, 2.0, 20), C_METAL, "Rail_S_W")
_box_static(Vector3(3.2, 4.5, 30), Vector3(0.3, 2.0, 20), C_METAL, "Rail_S_E")
func _build_central_platform() -> void:
# Raised platform (2u high, 16x16) — the main fight area
_box_static(Vector3(0, 1.0, 0), Vector3(16, 2.0, 16), C_METAL_DARK, "Central_Platform")
# Upper platform (4u high, 8x8) — sniper perch
_box_static(Vector3(0, 3.0, 0), Vector3(8, 2.0, 8), C_METAL, "Central_Upper")
# ── Ramps ─────────────────────────────────────────────────────────────────────
func _build_ramps() -> void:
# Ramps from ground to central platform (4 cardinal)
_ramp_static(Vector3(0, 1.5, -14), Vector3(8, 0.5, 8), Vector3(12, 0, 0), C_RAMP, "Ramp_CN")
_ramp_static(Vector3(0, 1.5, 14), Vector3(8, 0.5, 8), Vector3(-12, 0, 0), C_RAMP, "Ramp_CS")
_ramp_static(Vector3(14, 1.5, 0), Vector3(8, 0.5, 8), Vector3(0, 0, 12), C_RAMP, "Ramp_CE")
_ramp_static(Vector3(-14, 1.5, 0), Vector3(8, 0.5, 8), Vector3(0, 0, -12), C_RAMP, "Ramp_CW")
# Ramps from ground to corner towers
_ramp_static(Vector3(-36, 1.5, -28), Vector3(5, 0.5, 10), Vector3(15, 0, 0), C_WOOD, "Ramp_Tower_NW")
_ramp_static(Vector3(36, 1.5, -28), Vector3(5, 0.5, 10), Vector3(15, 0, 0), C_WOOD, "Ramp_Tower_NE")
_ramp_static(Vector3(-36, 1.5, 28), Vector3(5, 0.5, 10), Vector3(-15, 0, 0), C_WOOD, "Ramp_Tower_SW")
_ramp_static(Vector3(36, 1.5, 28), Vector3(5, 0.5, 10), Vector3(-15, 0, 0), C_WOOD, "Ramp_Tower_SE")
# Ramps to elevated walkways
_ramp_static(Vector3(0, 1.5, -20), Vector3(5, 0.5, 6), Vector3(10, 0, 0), C_RAMP, "Ramp_Walk_N")
_ramp_static(Vector3(0, 1.5, 20), Vector3(5, 0.5, 6), Vector3(-10, 0, 0), C_RAMP, "Ramp_Walk_S")
_ramp_static(Vector3(20, 1.5, 0), Vector3(6, 0.5, 5), Vector3(0, 0, 10), C_RAMP, "Ramp_Walk_E")
_ramp_static(Vector3(-20, 1.5, 0), Vector3(6, 0.5, 5), Vector3(0, 0, -10), C_RAMP, "Ramp_Walk_W")
# Quarter-pipe (curved ramp approximation) near center
for i in range(8):
var angle := float(i) * 11.25
var x_off := sin(deg_to_rad(angle)) * 6.0
var y_off := (1.0 - cos(deg_to_rad(angle))) * 6.0
_ramp_static(
Vector3(20 + x_off, y_off + 0.2, -20 + float(i) * 0.6),
Vector3(6, 0.3, 2),
Vector3(angle, 0, 0),
C_ORANGE,
"QuarterPipe_%d" % i
)
# ── Stepping Stones ───────────────────────────────────────────────────────────
func _build_stepping_stones() -> void:
# Parkour chain: NW quadrant
_box_static(Vector3(-12, 0.6, -12), Vector3(3, 1.2, 3), C_TEAL, "Step_NW_1")
_box_static(Vector3(-16, 1.5, -16), Vector3(3, 0.5, 3), C_TEAL, "Step_NW_2")
_box_static(Vector3(-20, 2.5, -20), Vector3(3, 0.5, 3), C_TEAL, "Step_NW_3")
_box_static(Vector3(-24, 3.5, -24), Vector3(3, 0.5, 3), C_TEAL, "Step_NW_4")
# Parkour chain: SE quadrant
_box_static(Vector3(12, 0.6, 12), Vector3(3, 1.2, 3), C_TEAL, "Step_SE_1")
_box_static(Vector3(16, 1.5, 16), Vector3(3, 0.5, 3), C_TEAL, "Step_SE_2")
_box_static(Vector3(20, 2.5, 20), Vector3(3, 0.5, 3), C_TEAL, "Step_SE_3")
_box_static(Vector3(24, 3.5, 24), Vector3(3, 0.5, 3), C_TEAL, "Step_SE_4")
# Parkour chain: NE quadrant (lower, for double-jump practice)
_box_static(Vector3(12, 0.6, -12), Vector3(3, 1.2, 3), C_PURPLE, "Step_NE_1")
_box_static(Vector3(16, 2.0, -16), Vector3(2.5, 0.5, 2.5), C_PURPLE, "Step_NE_2")
_box_static(Vector3(20, 3.5, -20), Vector3(2.5, 0.5, 2.5), C_PURPLE, "Step_NE_3")
# Parkour chain: SW quadrant
_box_static(Vector3(-12, 0.6, 12), Vector3(3, 1.2, 3), C_PURPLE, "Step_SW_1")
_box_static(Vector3(-16, 2.0, 16), Vector3(2.5, 0.5, 2.5), C_PURPLE, "Step_SW_2")
_box_static(Vector3(-20, 3.5, 20), Vector3(2.5, 0.5, 2.5), C_PURPLE, "Step_SW_3")
# ── Wall-Run Surfaces ────────────────────────────────────────────────────────
func _build_wall_run_surfaces() -> void:
# Dedicated wall-run corridor (North side, between walkway and north wall)
# Left wall
_box_static(Vector3(-8, 6, -38), Vector3(0.5, 12, 16), C_WALLRUN, "WallRunWall_L")
# Right wall
_box_static(Vector3(8, 6, -38), Vector3(0.5, 12, 16), C_WALLRUN, "WallRunWall_R")
# Obstacles in corridor to jump over
_box_static(Vector3(0, 0.75, -34), Vector3(16, 1.5, 1.0), C_RED, "WallRun_Obs1")
_box_static(Vector3(0, 0.75, -42), Vector3(16, 1.5, 1.0), C_RED, "WallRun_Obs2")
# Floor gap in corridor (forces wall-run)
_box_static(Vector3(-5, -0.75, -38), Vector3(6, 0.5, 16), C_FLOOR, "WallRun_Floor_L")
_box_static(Vector3(5, -0.75, -38), Vector3(6, 0.5, 16), C_FLOOR, "WallRun_Floor_R")
# Wall-run sections on outer walls (lower portions)
_box_static(Vector3(-40, 4, -47.5), Vector3(16, 8, 0.5), C_WALLRUN, "OuterWallRun_N1")
_box_static(Vector3(40, 4, -47.5), Vector3(16, 8, 0.5), C_WALLRUN, "OuterWallRun_N2")
_box_static(Vector3(-40, 4, 47.5), Vector3(16, 8, 0.5), C_WALLRUN, "OuterWallRun_S1")
_box_static(Vector3(40, 4, 47.5), Vector3(16, 8, 0.5), C_WALLRUN, "OuterWallRun_S2")
_box_static(Vector3(-47.5, 4, -40), Vector3(0.5, 8, 16), C_WALLRUN, "OuterWallRun_W1")
_box_static(Vector3(-47.5, 4, 40), Vector3(0.5, 8, 16), C_WALLRUN, "OuterWallRun_W2")
_box_static(Vector3(47.5, 4, -40), Vector3(0.5, 8, 16), C_WALLRUN, "OuterWallRun_E1")
_box_static(Vector3(47.5, 4, 40), Vector3(0.5, 8, 16), C_WALLRUN, "OuterWallRun_E2")
# ── Wall-Climb Surfaces ──────────────────────────────────────────────────────
func _build_wall_climb_surfaces() -> void:
# Textured walls for wall-climbing (distinct warm color)
# Around central tower
_box_static(Vector3(-7, 4, 0), Vector3(0.5, 6, 6), C_WALLCLIMB, "WallClimb_C1")
_box_static(Vector3(7, 4, 0), Vector3(0.5, 6, 6), C_WALLCLIMB, "WallClimb_C2")
# Mid-map wall-climb walls
_box_static(Vector3(-24, 4, 0), Vector3(0.5, 8, 8), C_WALLCLIMB, "WallClimb_W")
_box_static(Vector3(24, 4, 0), Vector3(0.5, 8, 8), C_WALLCLIMB, "WallClimb_E")
# ── Grapple Anchors ───────────────────────────────────────────────────────────
func _build_grapple_anchors() -> void:
# Tall pillar structures that serve as grapple targets
# Each is a tall, visible pillar with a distinct top
var pillar_positions := [
Vector3(-30, 0, 0),
Vector3(30, 0, 0),
Vector3(0, 0, -30),
Vector3(0, 0, 30),
]
for i in range(pillar_positions.size()):
var pos: Vector3 = pillar_positions[i]
# Pillar shaft (15u tall)
_box_static(pos + Vector3(0, 7.5, 0), Vector3(2, 15, 2), C_METAL, "GrapplePillar_%d" % i)
# Pillar top (grapple target marker)
_box_static(pos + Vector3(0, 15.5, 0), Vector3(4, 1.0, 4), C_ACCENT_WARM, "GrappleTop_%d" % i)
# Pillar base
_box_static(pos + Vector3(0, 0.5, 0), Vector3(4, 1.0, 4), C_METAL_DARK, "GrappleBase_%d" % i)
# 4 cardinal ramps from ground to central platform
# North ramp (slopes down toward -z)
_ramp_static(Vector3(0, 1.0, -11), Vector3(6, 0.4, 6), Vector3(14, 0, 0), C_RAMP, "Ramp_N")
# South ramp
_ramp_static(Vector3(0, 1.0, 11), Vector3(6, 0.4, 6), Vector3(-14, 0, 0), C_RAMP, "Ramp_S")
# East ramp
_ramp_static(Vector3(11, 1.0, 0), Vector3(6, 0.4, 6), Vector3(0, 0, -14), C_RAMP, "Ramp_E")
# West ramp
_ramp_static(Vector3(-11, 1.0, 0), Vector3(6, 0.4, 6), Vector3(0, 0, 14), C_RAMP, "Ramp_W")
# ── Cover System ──────────────────────────────────────────────────────────────
func _build_cover_system() -> void:
# Low walls (1.5u) for crouch cover - central area
_box_static(Vector3(-8, 0.75, -8), Vector3(5, 1.5, 0.5), C_WALL, "Cover_C1")
_box_static(Vector3(8, 0.75, 8), Vector3(5, 1.5, 0.5), C_WALL, "Cover_C2")
_box_static(Vector3(-8, 0.75, 8), Vector3(0.5, 1.5, 5), C_WALL, "Cover_C3")
_box_static(Vector3(8, 0.75, -8), Vector3(0.5, 1.5, 5), C_WALL, "Cover_C4")
func _build_cover() -> void:
# Low walls (1.5u high) for crouch cover — placed in open areas, not overlapping anything
_box_static(Vector3(-12, 0.75, -12), Vector3(4, 1.5, 0.5), C_WALL, "Cover_NW")
_box_static(Vector3(12, 0.75, 12), Vector3(4, 1.5, 0.5), C_WALL, "Cover_SE")
_box_static(Vector3(-12, 0.75, 12), Vector3(0.5, 1.5, 4), C_WALL, "Cover_SW")
_box_static(Vector3(12, 0.75, -12), Vector3(0.5, 1.5, 4), C_WALL, "Cover_NE")
# Crate clusters
_crate(Vector3(-15, 0.6, 0))
_crate(Vector3(-15, 0.6, 1.4))
_crate(Vector3(-15, 1.2, 0.7))
# Mid-map corridor walls — create lanes without blocking movement
_box_static(Vector3(-22, 2.0, -8), Vector3(12, 4.0, 0.4), C_WALL_DARK, "LaneWall_W")
_box_static(Vector3(22, 2.0, 8), Vector3(12, 4.0, 0.4), C_WALL_DARK, "LaneWall_E")
_crate(Vector3(15, 0.6, 0))
_crate(Vector3(15, 0.6, -1.4))
_crate(Vector3(15, 1.2, -0.7))
# Crate clusters — properly spaced, no overlaps
# NW cluster
_box_static(Vector3(-20, 0.6, -20), Vector3(1.2, 1.2, 1.2), C_CRATE, "Crate_NW1")
_box_static(Vector3(-20, 0.6, -21.5), Vector3(1.2, 1.2, 1.2), C_CRATE, "Crate_NW2")
_box_static(Vector3(-21.5, 0.6, -20), Vector3(1.2, 1.2, 1.2), C_CRATE, "Crate_NW3")
# SE cluster
_box_static(Vector3(20, 0.6, 20), Vector3(1.2, 1.2, 1.2), C_CRATE, "Crate_SE1")
_box_static(Vector3(20, 0.6, 21.5), Vector3(1.2, 1.2, 1.2), C_CRATE, "Crate_SE2")
_box_static(Vector3(21.5, 0.6, 20), Vector3(1.2, 1.2, 1.2), C_CRATE, "Crate_SE3")
# Center-side crates
_box_static(Vector3(-8, 0.6, 0), Vector3(1.2, 1.2, 1.2), C_CRATE, "Crate_C1")
_box_static(Vector3(8, 0.6, 0), Vector3(1.2, 1.2, 1.2), C_CRATE, "Crate_C2")
_crate(Vector3(0, 0.6, -15))
_crate(Vector3(1.4, 0.6, -15))
_crate(Vector3(0.7, 1.2, -15))
_crate(Vector3(0, 0.6, 15))
_crate(Vector3(-1.4, 0.6, 15))
_crate(Vector3(-0.7, 1.2, 15))
# Corridor walls (create engagement lanes)
_box_static(Vector3(-20, 2.0, -10), Vector3(16, 4.0, 0.5), C_WALL_DARK, "LaneWall_W1")
_box_static(Vector3(-20, 2.0, 10), Vector3(16, 4.0, 0.5), C_WALL_DARK, "LaneWall_W2")
_box_static(Vector3(20, 2.0, -10), Vector3(16, 4.0, 0.5), C_WALL_DARK, "LaneWall_E1")
_box_static(Vector3(20, 2.0, 10), Vector3(16, 4.0, 0.5), C_WALL_DARK, "LaneWall_E2")
# Structural pillars (cover in open areas)
_pillar(Vector3(-14, 3.5, -14))
_pillar(Vector3(14, 3.5, -14))
_pillar(Vector3(-14, 3.5, 14))
_pillar(Vector3(14, 3.5, 14))
func _crate(pos: Vector3) -> void:
_place_prop(prop_crate, pos, "Crate")
func _pillar(pos: Vector3) -> void:
_place_prop(prop_pillar, pos, "Pillar")
# ── Speed Corridor ────────────────────────────────────────────────────────────
func _build_speed_corridor() -> void:
# Long straight for sprint -> slide -> bunny-hop chains
var corridor_color := C_DARK_GREEN
# Side rails
_box_static(Vector3(38, 2.0, -20), Vector3(0.4, 4, 30), corridor_color, "SpeedRail_L")
_box_static(Vector3(44, 2.0, -20), Vector3(0.4, 4, 30), corridor_color, "SpeedRail_R")
# Speed bumps
for i in range(-15, 16, 8):
_box_static(Vector3(41, 0.2, float(i)), Vector3(6, 0.4, 0.6), C_YELLOW, "SpeedBump_%d" % i)
# Floor markings
for i in range(-12, 13, 4):
_box_static(Vector3(41, 0.02, float(i)), Vector3(4, 0.02, 0.3), C_YELLOW, "SpeedMark_%d" % i)
# Barrels — placed along walls, not blocking paths
_box_static(Vector3(-35, 0.5, -5), Vector3(0.8, 1.0, 0.8), Color(0.50, 0.45, 0.30), "Barrel_1")
_box_static(Vector3(-35, 0.5, 5), Vector3(0.8, 1.0, 0.8), Color(0.50, 0.45, 0.30), "Barrel_2")
_box_static(Vector3(35, 0.5, -5), Vector3(0.8, 1.0, 0.8), Color(0.50, 0.45, 0.30), "Barrel_3")
_box_static(Vector3(35, 0.5, 5), Vector3(0.8, 1.0, 0.8), Color(0.50, 0.45, 0.30), "Barrel_4")
# ── Spawn Alcoves ─────────────────────────────────────────────────────────────
# 6 spawns: 3 red (NW, SW, West-mid), 3 blue (NE, SE, East-mid)
# Each spawn is in a covered alcove with 2+ exit routes.
# 4 spawns: 2 red (NW, SW), 2 blue (NE, SE)
# Each spawn has 2 cover walls forming an L-shape, with 2 clear exit routes
func _build_spawn_alcoves() -> void:
# ── Red Spawns ──
var spawn_y := 0.01
# Red Spawn 1: NW corner
_spawn_floor(Vector3(-42, 0.01, -42), C_RED, "Spawn_Red_1")
_spawn_cover(Vector3(-40, 1.5, -45), Vector3(6, 3.0, 0.4), C_RED, "SpawnCover_R1A")
_spawn_cover(Vector3(-45, 1.5, -40), Vector3(0.4, 3.0, 6), C_RED, "SpawnCover_R1B")
_spawn_cover(Vector3(-38, 1.5, -38), Vector3(0.4, 3.0, 4), C_WALL_DARK, "SpawnCover_R1C")
# Red Spawn 1: NW corner — L-wall on north and west sides
_spawn_floor(Vector3(-35, spawn_y, -35), C_RED, "Spawn_Red_1")
_spawn_cover(Vector3(-33, 1.5, -39.5), Vector3(6, 3.0, 0.4), C_RED, "SCover_R1A") # North wall
_spawn_cover(Vector3(-39.5, 1.5, -33), Vector3(0.4, 3.0, 6), C_RED, "SCover_R1B") # West wall
# Exits: southeast (open)
# Red Spawn 2: SW corner
_spawn_floor(Vector3(-42, 0.01, 42), C_RED, "Spawn_Red_2")
_spawn_cover(Vector3(-40, 1.5, 45), Vector3(6, 3.0, 0.4), C_RED, "SpawnCover_R2A")
_spawn_cover(Vector3(-45, 1.5, 40), Vector3(0.4, 3.0, 6), C_RED, "SpawnCover_R2B")
_spawn_cover(Vector3(-38, 1.5, 38), Vector3(0.4, 3.0, 4), C_WALL_DARK, "SpawnCover_R2C")
# Red Spawn 2: SW corner — L-wall on south and west sides
_spawn_floor(Vector3(-35, spawn_y, 35), C_RED, "Spawn_Red_2")
_spawn_cover(Vector3(-33, 1.5, 39.5), Vector3(6, 3.0, 0.4), C_RED, "SCover_R2A") # South wall
_spawn_cover(Vector3(-39.5, 1.5, 33), Vector3(0.4, 3.0, 6), C_RED, "SCover_R2B") # West wall
# Exits: northeast (open)
# Red Spawn 3: West mid
_spawn_floor(Vector3(-44, 0.01, 0), C_RED, "Spawn_Red_3")
_spawn_cover(Vector3(-42, 1.5, -3), Vector3(0.4, 3.0, 6), C_RED, "SpawnCover_R3A")
_spawn_cover(Vector3(-42, 1.5, 3), Vector3(0.4, 3.0, 6), C_RED, "SpawnCover_R3B")
_spawn_cover(Vector3(-40, 1.5, 0), Vector3(4, 3.0, 0.4), C_WALL_DARK, "SpawnCover_R3C")
# Blue Spawn 1: NE corner — L-wall on north and east sides
_spawn_floor(Vector3(35, spawn_y, -35), C_BLUE, "Spawn_Blue_1")
_spawn_cover(Vector3(33, 1.5, -39.5), Vector3(6, 3.0, 0.4), C_BLUE, "SCover_B1A") # North wall
_spawn_cover(Vector3(39.5, 1.5, -33), Vector3(0.4, 3.0, 6), C_BLUE, "SCover_B1B") # East wall
# Exits: southwest (open)
# ── Blue Spawns ──
# Blue Spawn 1: NE corner
_spawn_floor(Vector3(42, 0.01, -42), C_BLUE, "Spawn_Blue_1")
_spawn_cover(Vector3(40, 1.5, -45), Vector3(6, 3.0, 0.4), C_BLUE, "SpawnCover_B1A")
_spawn_cover(Vector3(45, 1.5, -40), Vector3(0.4, 3.0, 6), C_BLUE, "SpawnCover_B1B")
_spawn_cover(Vector3(38, 1.5, -38), Vector3(0.4, 3.0, 4), C_WALL_DARK, "SpawnCover_B1C")
# Blue Spawn 2: SE corner
_spawn_floor(Vector3(42, 0.01, 42), C_BLUE, "Spawn_Blue_2")
_spawn_cover(Vector3(40, 1.5, 45), Vector3(6, 3.0, 0.4), C_BLUE, "SpawnCover_B2A")
_spawn_cover(Vector3(45, 1.5, 40), Vector3(0.4, 3.0, 6), C_BLUE, "SpawnCover_B2B")
_spawn_cover(Vector3(38, 1.5, 38), Vector3(0.4, 3.0, 4), C_WALL_DARK, "SpawnCover_B2C")
# Blue Spawn 3: East mid
_spawn_floor(Vector3(44, 0.01, 0), C_BLUE, "Spawn_Blue_3")
_spawn_cover(Vector3(42, 1.5, -3), Vector3(0.4, 3.0, 6), C_BLUE, "SpawnCover_B3A")
_spawn_cover(Vector3(42, 1.5, 3), Vector3(0.4, 3.0, 6), C_BLUE, "SpawnCover_B3B")
_spawn_cover(Vector3(40, 1.5, 0), Vector3(4, 3.0, 0.4), C_WALL_DARK, "SpawnCover_B3C")
# Blue Spawn 2: SE corner — L-wall on south and east sides
_spawn_floor(Vector3(35, spawn_y, 35), C_BLUE, "Spawn_Blue_2")
_spawn_cover(Vector3(33, 1.5, 39.5), Vector3(6, 3.0, 0.4), C_BLUE, "SCover_B2A") # South wall
_spawn_cover(Vector3(39.5, 1.5, 33), Vector3(0.4, 3.0, 6), C_BLUE, "SCover_B2B") # East wall
# Exits: northwest (open)
func _spawn_floor(pos: Vector3, color: Color, label: String) -> void:
@@ -548,81 +264,27 @@ func _spawn_cover(pos: Vector3, size: Vector3, color: Color, label: String) -> v
_box_static(pos, size, color, label)
# ── Decorations ───────────────────────────────────────────────────────────────
func _build_decorations() -> void:
# Accent beams on outer walls
for i in range(-5, 6):
var z_pos := float(i) * 8.0
_box_static(Vector3(-47.5, 9.0, z_pos), Vector3(0.3, 0.3, 5.0), C_ACCENT_WARM, "AccentBeam_W_%d" % i)
_box_static(Vector3(47.5, 9.0, z_pos), Vector3(0.3, 0.3, 5.0), C_ACCENT_COOL, "AccentBeam_E_%d" % i)
# Hazard stripes on ramp edges
for i in range(4):
var offset := -2.5 + float(i) * 1.5
_box_static(Vector3(offset, 0.05, -17.8), Vector3(1.0, 0.1, 0.4), C_YELLOW, "Hazard_CN_%d" % i)
_box_static(Vector3(offset, 0.05, 17.8), Vector3(1.0, 0.1, 0.4), C_YELLOW, "Hazard_CS_%d" % i)
# Barrels
_barrel(Vector3(-30, 0.5, -5))
_barrel(Vector3(-30, 0.5, -6.5))
_barrel(Vector3(30, 0.5, 5))
_barrel(Vector3(30, 0.5, 6.5))
_barrel(Vector3(5, 0.5, -30))
_barrel(Vector3(-5, 0.5, 30))
_barrel(Vector3(-25, 0.5, 25))
_barrel(Vector3(25, 0.5, -25))
# Pickup props (GLB)
_place_prop(prop_weapon_pickup, Vector3(0, 0.05, -25), "WeaponPickup_Center")
_place_prop(prop_health_pack, Vector3(-20, 0.05, 0), "HealthPack_West")
_place_prop(prop_health_pack, Vector3(20, 0.05, 0), "HealthPack_East")
_place_prop(prop_ammo_pack, Vector3(0, 0.05, 25), "AmmoPack_Center")
_place_prop(prop_ammo_pack, Vector3(-35, 0.05, -35), "AmmoPack_NW")
_place_prop(prop_ammo_pack, Vector3(35, 0.05, 35), "AmmoPack_SE")
# Spawn area lights
_spawn_area_light(Vector3(-42, 3, -42), C_ACCENT_WARM, 6.0, 0.8)
_spawn_area_light(Vector3(-42, 3, 42), C_ACCENT_WARM, 6.0, 0.8)
_spawn_area_light(Vector3(-44, 3, 0), C_ACCENT_WARM, 6.0, 0.8)
_spawn_area_light(Vector3(42, 3, -42), C_ACCENT_COOL, 6.0, 0.8)
_spawn_area_light(Vector3(42, 3, 42), C_ACCENT_COOL, 6.0, 0.8)
_spawn_area_light(Vector3(44, 3, 0), C_ACCENT_COOL, 6.0, 0.8)
func _barrel(pos: Vector3) -> void:
_place_prop(prop_barrel, pos, "Barrel")
func _spawn_area_light(pos: Vector3, color: Color, radius: float, energy: float) -> void:
var light := OmniLight3D.new()
light.name = "SpawnLight"
light.position = pos
light.light_color = color
light.light_energy = energy
light.omni_range = radius
light.shadow_enabled = false
add_child(light)
# ── Dummies ───────────────────────────────────────────────────────────────────
func _build_target_dummy() -> void:
func _build_dummies() -> void:
# Target dummy — in open area south of center
var dummy = load("res://entities/target_dummy.gd").new()
dummy.name = "TargetDummy"
dummy.position = Vector3(0, 0, -15)
dummy.position = Vector3(0, 0, -25)
add_child(dummy)
# Killable dummy — in open area east of center
var killable = load("res://entities/killable_dummy.gd").new()
killable.name = "KillableDummy"
killable.position = Vector3(5, 0, -15)
killable.position = Vector3(25, 0, 0)
add_child(killable)
# Walking dummy — patrols along the south side
var walking = load("res://entities/walking_dummy.gd").new()
walking.name = "WalkingDummy"
walking.position = Vector3(20, 0, -15)
walking.point_a = Vector3(20, 0, -15)
walking.point_b = Vector3(20, 0, 15)
walking.position = Vector3(-20, 0, -30)
walking.point_a = Vector3(-20, 0, -30)
walking.point_b = Vector3(20, 0, -30)
add_child(walking)
@@ -636,7 +298,7 @@ func _build_lighting() -> void:
sun.light_energy = 1.2
sun.shadow_enabled = true
sun.directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS
sun.directional_shadow_max_distance = 120.0
sun.directional_shadow_max_distance = 100.0
add_child(sun)
var fill := DirectionalLight3D.new()
@@ -647,16 +309,6 @@ func _build_lighting() -> void:
fill.shadow_enabled = false
add_child(fill)
# Central area accent light
var center_light := OmniLight3D.new()
center_light.name = "CenterLight"
center_light.position = Vector3(0, 10, 0)
center_light.light_color = Color(0.9, 0.85, 0.7)
center_light.light_energy = 0.6
center_light.omni_range = 25.0
center_light.shadow_enabled = false
add_child(center_light)
# ── Player ────────────────────────────────────────────────────────────────────
@@ -666,7 +318,7 @@ func _spawn_player(pid: int) -> CharacterBody3D:
player.set_multiplayer_authority(pid)
# Use a slightly random spawn position to avoid exact overlapping
player.position = Vector3(randf_range(-2, 2), 2.0, randf_range(-2, 2))
player.position = Vector3(randf_range(-5, 5), 1.0, 20)
# Server Synchronizer (Host is the ground truth)
var server_sync = MultiplayerSynchronizer.new()