extends Node3D signal matador_killed signal all_defeated const MATADOR := preload("res://Matador.tscn") var _spawn_count: int = 1 var _alive: int = 0 var _resolved: bool = false func _ready() -> void: add_to_group(&"matador_spawn") _spawn_count = maxi(1, int(DP.f("mat_spawn_count"))) # Deferred so it's safe from _ready and matches the previous spawn timing. _spawn.call_deferred() func _spawn() -> void: # Bull and matadors start at opposite ends of a random diameter, so they always # begin as far apart as the arena allows (edge-to-edge across the centre). var arena_r := DP.f("arena_spawn_radius") var bull_angle := randf() * TAU _place_bull(bull_angle, arena_r) for pos: Vector3 in _spread_positions(_spawn_count, bull_angle + PI, arena_r): _spawn_at(pos) 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 ) # Positions for the matadors, clustered on the far side (centred on `center_angle`) # so every matador stays near the point diametrically opposite the bull. func _spread_positions(count: int, center_angle: float, arena_r: float) -> Array[Vector3]: var positions: Array[Vector3] = [] if count <= 1: positions.append(Vector3(cos(center_angle) * arena_r, 1.0, sin(center_angle) * arena_r)) return positions var arc := TAU * 0.33 for i: int in count: var t := float(i) / float(count - 1) - 0.5 # -0.5 .. 0.5 across the arc var angle := center_angle + t * arc var radius := randf_range(arena_r * 0.7, arena_r) positions.append(Vector3(cos(angle) * radius, 1.0, sin(angle) * radius)) return positions func _spawn_at(pos: Vector3) -> void: var m: Node3D = MATADOR.instantiate() m.position = pos get_parent().add_child(m) m.killed.connect(_on_matador_killed) _alive += 1 # Every matador down = the bull wins. No waves, no respawns — one clean arena. func _on_matador_killed() -> void: matador_killed.emit() _alive -= 1 if _alive <= 0 and not _resolved: _resolved = true all_defeated.emit()