extends Node3D signal matador_killed signal all_defeated const MATADOR := preload("res://Matador.tscn") var _alive: int = 0 var _wave: int = 0 var _resolved: bool = false var _spawn_points: Array[Node3D] = [] func _ready() -> void: add_to_group(&"matador_spawn") _spawn.call_deferred() # Each spawner node is a spawn-point marker. The first in tree order (the one the HUD # connects its all_defeated to) runs the wave logic and uses every marker — itself and # its siblings — as an entry point; the rest stay passive so waves aren't run four times. func _spawn() -> void: var markers := get_tree().get_nodes_in_group(&"matador_spawn") if markers.is_empty() or markers[0] != self: return for node: Node in markers: if node is Node3D: _spawn_points.append(node as Node3D) _place_bull(randf() * TAU, DP.f("arena_spawn_radius")) _start_next_wave() # Wave 1 sends 1 matador, wave 2 sends 2, … up to the number of placed spawn points. # Matadors are assigned to a random subset of spawn points each wave and enter staggered. func _start_next_wave() -> void: _wave += 1 # 8 waves total. if _wave > DP.f("mat_wave_size"): return # Wave 1 = 1 matador, Wave 2 = 2, ... Wave 8 = 8. var count := _wave # Shuffle the spawn points so the spawn locations are random. var points: Array[Node3D] = _spawn_points.duplicate() points.shuffle() for i in range(count): # Reuse spawn points when we have more matadors than points. var sp: Node3D = points[i % points.size()] var m := _spawn_at(sp.global_position) m.call_deferred(&"enter_spawn_roll", Vector3.ZERO) if i < count - 1: await get_tree().create_timer( DP.f("mat_spawn_stagger") ).timeout func _place_bull(angle: float, arena_r: float) -> void: var bull := get_tree().get_first_node_in_group(&"player") as Node3D if not bull: return bull.global_position = Vector3( cos(angle) * arena_r, bull.global_position.y, sin(angle) * arena_r ) func _spawn_at(pos: Vector3) -> Node3D: var m: Node3D = MATADOR.instantiate() m.position = pos get_parent().add_child(m) m.killed.connect(_on_matador_killed) _alive += 1 return m func _on_matador_killed() -> void: matador_killed.emit() _alive -= 1 if _alive > 0 or _resolved: return if _wave >= DP.f("mat_wave_size"): DP.set_value("gate_unlocked_bear", true) _resolved = true all_defeated.emit() else: await get_tree().create_timer(DP.f("mat_wave_pause")).timeout _start_next_wave()