GameLogic/ArenaDirector.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;

public sealed class ArenaDirector : Component
{
	[Property, Title( "Portes de l'arène" )] 
	public List<ArenaDoor> Gates { get; set; } = new();

	[Property] public Color[] LegionColors { get; set; } = new[] 
	{ 
		new Color( 0.65f, 0.12f, 0.12f ), // Pourpre romain
		new Color( 0.15f, 0.35f, 0.65f ), // Bleu cobalt
		new Color( 0.75f, 0.55f, 0.15f ), // Ocre / Or
		new Color( 0.20f, 0.45f, 0.25f )  // Vert antique
	};

	public readonly List<Fighter> ActiveFighters = new();
	private readonly List<RivalData> _persistentRivals = new();

	private static readonly string[] RomanNames = new[]
	{
		"Spiculus", "Flamma", "Crixus", "Tetraites", "Priscus", 
		"Verus", "Carpophorus", "Marcus", "Hermes", "Severus",
		"Attilius", "Commodus", "Spartacus", "Hilarus", "Celadus"
	};

	private RoundConfig _currentConfig;
	private int _spawnedThisRound = 0;
	private bool _isSpawningActive = false;

	// Propriétés d'état consultables par le GameManager et l'UI
	public int RemainingQuota => _currentConfig != null ? Math.Max( 0, _currentConfig.TotalCombatants - _spawnedThisRound ) : 0;
	public int LivingEnemiesCount => ActiveFighters.Count( f => f.IsValid && !f.IsDead );
	
	// La manche n'est gagnée par élimination QUE si tous les combattants prévus sont apparus ET sont morts
	public bool IsWaveCleared => _currentConfig != null && _spawnedThisRound >= _currentConfig.TotalCombatants && LivingEnemiesCount == 0;

	public void InitializeRound( RoundConfig config )
	{
		_currentConfig = config;
		_spawnedThisRound = 0;
		ActiveFighters.Clear();
		_isSpawningActive = true;

		Log.Info( $"🎪 [DIRECTOR] Initialisation manche : {_currentConfig.RoundTitle} | Total: {_currentConfig.TotalCombatants} gladiateurs (Max simultanés: {_currentConfig.MaxConcurrentFighters})" );
		
		MaintainArenaPopulation();
	}

	public void StopSpawningAndPacify()
	{
		_isSpawningActive = false;

		// Stoppe l'agressivité des survivants
		foreach ( var fighter in ActiveFighters )
		{
			if ( !fighter.IsValid || fighter.IsDead ) continue;

			var agent = fighter.Components.Get<NavMeshAgent>();
			if ( agent != null && agent.IsValid ) agent.Enabled = false;

			var combat = fighter.Components.Get<FighterCombat>();
			combat?.CancelAnticipation();
		}
	}

	public void ClearRemainingEnemies()
	{
		foreach ( var fighter in ActiveFighters.ToList() )
		{
			if ( fighter.IsValid && !fighter.IsDead )
				fighter.GameObject.Destroy();
		}
		ActiveFighters.Clear();
	}

	public void RestoreRivals( List<RivalData> rivals )
	{
		_persistentRivals.Clear();
		if ( rivals != null )
		{
			_persistentRivals.AddRange( rivals.Where( r => !r.IsDead ) );
		}
	}

	public List<RivalData> ExportSurvivingRivals()
	{
		var list = new List<RivalData>();

		foreach ( var fighter in ActiveFighters )
		{
			if ( !fighter.IsValid || fighter.IsDead ) continue;

			var identity = fighter.Components.Get<GladiatorIdentity>();
			var aura = fighter.Components.Get<FighterAura>();

			if ( identity != null )
			{
				identity.RoundsSurvived++;
				list.Add( identity.ToData( fighter, aura ) );
			}
		}

		foreach ( var stored in _persistentRivals )
		{
			if ( !list.Any( r => r.Name == stored.Name ) )
			{
				list.Add( stored );
			}
		}

		return list;
	}

	private async void MaintainArenaPopulation()
	{
		while ( _isSpawningActive && _currentConfig != null )
		{
			ActiveFighters.RemoveAll( f => !f.IsValid || f.IsDead );

			// Remplissage rapide jusqu'à atteindre le quota simultané
			while ( ActiveFighters.Count < _currentConfig.MaxConcurrentFighters && _spawnedThisRound < _currentConfig.TotalCombatants && _isSpawningActive )
			{
				await SpawnSingleGladiator();
				// Petit décalage pour ne pas ouvrir toutes les herses à la même milliseconde
				await Task.Delay( 600 );
			}

			// Attente avant la prochaine vérification de renforts
			await Task.Delay( (int)(_currentConfig.RespawnDelay * 1000f) );
		}
	}

	private async Task SpawnSingleGladiator()
	{
		if ( Gates.Count == 0 || _currentConfig.EnemyPrefabs.Count == 0 ) return;

		// Sélection aléatoire parmi les portes valides
		var validGates = Gates.Where( g => g != null && g.IsValid ).ToList();
		if ( validGates.Count == 0 ) return;

		var chosenGate = validGates[Random.Shared.Next( validGates.Count )];
		var prefab = _currentConfig.EnemyPrefabs[Random.Shared.Next( _currentConfig.EnemyPrefabs.Count )];

		_ = chosenGate.OpenDoorAsync( autoCloseAfterSeconds: 4.5f );

		var botObj = prefab.Clone( chosenGate.SpawnPosition, chosenGate.SpawnRotation );
		var fighter = botObj.Components.Get<Fighter>();

		if ( fighter != null )
		{
			ActiveFighters.Add( fighter );
			_spawnedThisRound++;
			ApplyRandomGladiatorAttributes( fighter );

			Log.Info( $"⚔️ [SPAWN] {fighter.GameObject.Name} entre en lice ! ({_spawnedThisRound}/{_currentConfig.TotalCombatants})" );
		}

		await Task.Delay( 800 );
	}

	private void ApplyRandomGladiatorAttributes( Fighter fighter )
	{
		var identity = fighter.Components.GetOrCreate<GladiatorIdentity>();
		var aura = fighter.Components.Get<FighterAura>();

		if ( _persistentRivals.Count > 0 )
		{
			var rival = _persistentRivals[0];
			_persistentRivals.RemoveAt( 0 );

			identity.RestoreFromData( rival, fighter, aura );

			fighter.MaxHealth += 10f;
			fighter.CurrentHealth = fighter.MaxHealth;
			fighter.BaseBalanceRegenRate += 5f;
		}
		else
		{
			identity.FighterName = RomanNames[Random.Shared.Next( RomanNames.Length )];
			identity.LegionColorIndex = Random.Shared.Next( LegionColors.Length );

			float healthVariation = Random.Shared.Float( 0.85f, 1.25f );
			fighter.MaxHealth *= healthVariation;
			fighter.CurrentHealth = fighter.MaxHealth;
		}

		fighter.GameObject.Name = identity.FighterName;

		if ( LegionColors.Length > 0 )
		{
			var chosenColor = LegionColors[identity.LegionColorIndex % LegionColors.Length];
			foreach ( var renderer in fighter.Components.GetAll<SkinnedModelRenderer>() )
			{
				if ( renderer.IsValid && renderer.SceneObject != null )
				{
					renderer.SceneObject.Attributes.Set( "ColorTint", chosenColor );
				}
			}
		}
	}
}