70 lines
2.1 KiB
GDScript
70 lines
2.1 KiB
GDScript
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()
|