## WaveManager.gd ## Loads wave definitions and orchestrates creep spawning along the lane path. extends Node const WAVE_DATA_PATH := "res://data/waves.json" const TOTAL_WAVES := 31 var _waves: Array = [] var _active_creeps: int = 0 func _ready() -> void: _load_waves() EventBus.creep_died.connect(_on_creep_removed) EventBus.creep_reached_exit.connect(_on_creep_removed) func _load_waves() -> void: _waves = DataLoader.load_waves() if _waves.is_empty(): push_error("WaveManager: waves.json failed to load or is empty") func start_wave(wave_number: int, lane: Node) -> void: if wave_number < 1 or wave_number > _waves.size(): push_error("WaveManager: invalid wave number %d" % wave_number) return var wave_data: Dictionary = _waves[wave_number - 1] GameState.advance_wave() GameState.set_phase(GameState.Phase.COMBAT) EventBus.wave_started.emit(wave_number) var count: int = wave_data.get("creep_count", 1) _active_creeps += count var path_pts: PackedVector2Array = lane.get_path_points() if lane.has_method("get_path_points") else PackedVector2Array() # Spawn creeps spaced 0.5 s apart for i in range(count): await get_tree().create_timer(0.5 * i).timeout _spawn_creep(wave_data, lane, path_pts) func _spawn_creep(wave_data: Dictionary, lane: Node, path_pts: PackedVector2Array) -> void: var creep_scene := load("res://scenes/gameplay/Creep.tscn") as PackedScene if not creep_scene: push_error("WaveManager: Creep.tscn not found") _active_creeps -= 1 return var creep: Node = creep_scene.instantiate() lane.add_child(creep) creep.setup(wave_data) if path_pts.size() > 0: # Convert lane-local path to global coords for the creep var global_pts := PackedVector2Array() for pt in path_pts: global_pts.append(lane.to_global(pt)) creep.assign_path(global_pts) func _on_creep_removed(_creep: Node, _value) -> void: _active_creeps = max(0, _active_creeps - 1) if _active_creeps == 0: _wave_complete() func _wave_complete() -> void: var wn := GameState.wave_number EventBus.wave_completed.emit(wn) Economy.process_income_phase() if wn < TOTAL_WAVES: GameState.set_phase(GameState.Phase.BUILD) # If wave 31, GameOver.gd listens to wave_completed and handles victory