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 );
}
}
}
}
}