GameLogic/GameManager.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;
public enum GamePhase
{
BufferZone, // Entraînement / Préparation, timer masqué
RoundIntro, // Portes qui s'ouvrent, gladiateurs qui entrent
RoundActive, // Combat en cours, timer actif
AwaitingVerdict, // Joueur au sol / mort, décision de l'Editor
RoundRecap, // Fin de manche, stats à l'écran
GameOver, // Exécution ou défaite définitive
Victory // Survie au terme des manches ou conquête du public (1 000 fans)
}
public sealed class GameManager : Component
{
public static GameManager Instance { get; private set; }
[Property, Group( "Liaisons" )] public PlayerController Player { get; set; }
[Property, Group( "Liaisons" )] public ArenaDirector Director { get; set; }
[Property, Group( "Liaisons" )] public ArenaDoor PlayerArenaDoor { get; set; }
[Property, Group( "Liaisons" )] public GameObject PlayerSpawnBuffer { get; set; }
[Property, Group( "Manches" )] public List<RoundConfig> Rounds { get; set; } = new();
[Property, Group( "Manches" )] public int CurrentRoundIndex { get; private set; } = 0;
// =========================================================================
// SYSTÈME DE PARTISANS (FANS)
// =========================================================================
public const int TargetFans = 1000;
public int TotalFans { get; private set; } = 0;
public int LastRoundFanGain { get; private set; } = 0;
public GamePhase CurrentPhase { get; private set; } = GamePhase.BufferZone;
public float TimeRemaining { get; private set; } = 0f;
public RoundConfig CurrentRound => (CurrentRoundIndex >= 0 && CurrentRoundIndex < Rounds.Count) ? Rounds[CurrentRoundIndex] : null;
public static event Action<GamePhase> OnPhaseChanged;
public static event Action<int> OnRoundChanged;
private TimeSince _timeSinceLastLog = 0f;
protected override void OnAwake()
{
Instance = this;
Player ??= Scene.GetAllComponents<PlayerController>().FirstOrDefault();
}
protected override void OnStart()
{
var save = SaveManager.Load();
if ( save != null )
{
CurrentRoundIndex = save.CurrentRoundIndex;
Director?.RestoreRivals( save.Rivals );
RestorePlayerData( save );
}
StartBufferZonePhase();
}
protected override void OnUpdate()
{
switch ( CurrentPhase )
{
case GamePhase.RoundActive:
UpdateRoundTimer();
CheckCombatConditions();
MonitorCombatConsole();
break;
case GamePhase.RoundRecap:
if ( Input.Pressed( "jump" ) || Input.Pressed( "use" ) )
{
Log.Info( "⏩ [INPUT] Validation du récapitulatif !" );
AdvanceToNextRoundOrBuffer();
}
break;
}
}
[Broadcast]
public async void TriggerEnterArena()
{
if ( CurrentPhase != GamePhase.BufferZone ) return;
if ( CurrentRound == null )
{
Log.Error( $"❌ [GAME MANAGER] Impossible d'entrer en arène : Aucune manche configurée pour l'index {CurrentRoundIndex} !" );
return;
}
ChangePhase( GamePhase.RoundIntro );
Director?.InitializeRound( CurrentRound );
await Task.Delay( 1500 );
if ( PlayerArenaDoor != null )
await PlayerArenaDoor.OpenDoorAsync( autoCloseAfterSeconds: 6.0f );
TimeRemaining = CurrentRound?.RoundDuration ?? 240f;
ChangePhase( GamePhase.RoundActive );
}
private void UpdateRoundTimer()
{
TimeRemaining -= Time.Delta;
if ( TimeRemaining <= 0f )
{
TimeRemaining = 0f;
Log.Info( "⏰ [GONG] Temps écoulé ! Fin de la manche." );
CompleteRound( playerWon: true );
}
}
private void CheckCombatConditions()
{
if ( Director != null && Director.IsWaveCleared )
{
Log.Info( "🏆 [CARNAGE] Tous les gladiateurs de la manche ont été éliminés !" );
CompleteRound( playerWon: true );
}
}
private void MonitorCombatConsole()
{
if ( _timeSinceLastLog >= 15.0f )
{
_timeSinceLastLog = 0f;
var aura = Player?.Components.Get<FighterAura>()?.CurrentAura ?? 0f;
Log.Info( $"⏱️ [COMBAT] Temps restant: {TimeRemaining:F0}s | Ennemis: {Director?.LivingEnemiesCount} (Quota: {Director?.RemainingQuota}) | Aura: {aura:F0}% | Fans: {TotalFans}/{TargetFans}" );
}
}
public void HandlePlayerKnockedDownOrKilled( Fighter playerFighter )
{
if ( CurrentPhase != GamePhase.RoundActive ) return;
ChangePhase( GamePhase.AwaitingVerdict );
CrowdManager.Instance?.EvaluateEditorVerdict( playerFighter, ( spared ) =>
{
if ( spared )
{
Log.Info( "🏛️ L'Editor accorde sa grâce !" );
CompleteRound( playerWon: false );
}
else
{
Log.Info( "💀 L'Editor ordonne l'exécution !" );
SaveManager.ClearSave();
ChangePhase( GamePhase.GameOver );
}
} );
}
public void CompleteRound( bool playerWon )
{
Director?.StopSpawningAndPacify();
// Calcul de conversion d'aura cachée (0% -> -100 fans | 100% -> +300 fans)
var aura = Player?.Components.Get<FighterAura>()?.CurrentAura ?? 0f;
LastRoundFanGain = (int)MathF.Round( MathX.Lerp( -100f, 300f, Math.Clamp( aura / 100f, 0f, 1f ) ) );
TotalFans = Math.Clamp( TotalFans + LastRoundFanGain, 0, TargetFans );
ChangePhase( GamePhase.RoundRecap );
Log.Info( "==========================================================" );
Log.Info( $"📊 [RÉCAP MANCHE {CurrentRoundIndex + 1}] {(playerWon ? "VICTOIRE" : "GRÂCE ACCORDÉE")}" );
Log.Info( $"🏛️ [FANS] Gain : {(LastRoundFanGain >= 0 ? "+" : "")}{LastRoundFanGain} | Total : {TotalFans}/{TargetFans}" );
Log.Info( "👉 Appuie sur [ESPACE] ou [E] pour retourner au camp d'entraînement." );
Log.Info( "==========================================================" );
}
public void AdvanceToNextRoundOrBuffer()
{
Director?.ClearRemainingEnemies();
CurrentRoundIndex++;
// Victoire si le joueur a conquis Rome (1 000 fans) OU terminé toutes les manches
if ( TotalFans >= TargetFans || CurrentRoundIndex >= Rounds.Count )
{
SaveManager.ClearSave();
ChangePhase( GamePhase.Victory );
Log.Info( "👑 [VICTOIRE FINALE] Rome est à vos pieds !" );
return;
}
OnRoundChanged?.Invoke( CurrentRoundIndex + 1 );
TeleportPlayerToBuffer();
StartBufferZonePhase();
}
private void StartBufferZonePhase()
{
ChangePhase( GamePhase.BufferZone );
PlayerArenaDoor?.CloseDoor();
var fighter = Player?.Components.Get<Fighter>();
if ( fighter != null && !fighter.IsDead )
{
fighter.CurrentBalance = fighter.MaxBalance;
}
Log.Info( $"🛡️ [CAMP D'ENTRAÎNEMENT] Préparation Manche {CurrentRoundIndex + 1}. Fans actuels : {TotalFans}/{TargetFans}." );
SaveCurrentProgress();
}
public void ResetCampaignProgression()
{
TotalFans = 0;
LastRoundFanGain = 0;
CurrentRoundIndex = 0;
var fighter = Player?.Components.Get<Fighter>();
if ( fighter != null )
{
fighter.MaxHealth = 100f;
fighter.CurrentHealth = 100f;
fighter.MaxBalance = 100f;
fighter.CurrentBalance = 100f;
}
var aura = Player?.Components.Get<FighterAura>();
if ( aura != null )
{
aura.ApplyAuraDelta( aura.StartingAura - aura.CurrentAura );
}
}
public void SaveCurrentProgress()
{
if ( Player == null ) Player = Scene.GetAllComponents<PlayerController>().FirstOrDefault();
var fighter = Player?.Components.Get<Fighter>();
var aura = Player?.Components.Get<FighterAura>();
var save = new CampaignSaveData
{
CurrentRoundIndex = CurrentRoundIndex,
PlayerAura = aura?.CurrentAura ?? 20f,
PlayerMaxHealth = fighter?.MaxHealth ?? 100f,
PlayerMaxBalance = fighter?.MaxBalance ?? 100f,
TotalFans = TotalFans,
Rivals = Director?.ExportSurvivingRivals() ?? new()
};
SaveManager.Save( save );
}
private void RestorePlayerData( CampaignSaveData save )
{
if ( Player == null ) Player = Scene.GetAllComponents<PlayerController>().FirstOrDefault();
if ( Player == null ) return;
TotalFans = save.TotalFans;
var fighter = Player.Components.Get<Fighter>();
if ( fighter != null )
{
fighter.MaxHealth = save.PlayerMaxHealth;
fighter.CurrentHealth = save.PlayerMaxHealth;
fighter.MaxBalance = save.PlayerMaxBalance;
fighter.CurrentBalance = save.PlayerMaxBalance;
}
var aura = Player.Components.Get<FighterAura>();
if ( aura != null )
{
float targetAura = save.PlayerAura <= 0f ? 20f : save.PlayerAura;
aura.ApplyAuraDelta( targetAura - aura.CurrentAura );
}
}
private void TeleportPlayerToBuffer()
{
if ( Player == null ) Player = Scene.GetAllComponents<PlayerController>().FirstOrDefault();
if ( Player == null ) return;
if ( PlayerSpawnBuffer == null )
{
Log.Warning( "⚠️ [GAME MANAGER] PlayerSpawnBuffer n'est pas assigné ! Le joueur n'a pas pu être téléporté." );
return;
}
var movement = Player.Components.Get<FighterMovement>();
if ( movement != null )
{
movement.WishVelocity = Vector3.Zero;
movement.WantsSprint = false;
movement.WantsCrouch = false;
}
var cc = Player.Components.Get<CharacterController>();
if ( cc != null && cc.IsValid )
{
cc.Velocity = Vector3.Zero;
}
var rb = Player.Components.Get<Rigidbody>();
if ( rb != null && rb.IsValid )
{
rb.Velocity = Vector3.Zero;
rb.AngularVelocity = Vector3.Zero;
}
// Élévation de sécurité de 10 unités pour ne pas fusionner avec le collider du sol
Player.WorldPosition = PlayerSpawnBuffer.WorldPosition + Vector3.Up * 10.0f;
Player.WorldRotation = PlayerSpawnBuffer.WorldRotation;
if ( cc != null && cc.IsValid )
{
cc.Punch( Vector3.Down * 0.1f );
}
}
private void ChangePhase( GamePhase newPhase )
{
CurrentPhase = newPhase;
OnPhaseChanged?.Invoke( newPhase );
}
public void StartCustomRound( RoundConfig customRound )
{
if ( customRound == null ) return;
if ( (customRound.EnemyPrefabs == null || customRound.EnemyPrefabs.Count == 0) && Rounds.Count > 0 )
{
customRound.EnemyPrefabs = Rounds[0].EnemyPrefabs;
}
Rounds = new List<RoundConfig> { customRound };
CurrentRoundIndex = 0;
Director?.ClearRemainingEnemies();
TeleportPlayerToBuffer();
StartBufferZonePhase();
Log.Info( $"🎪 [FREE PLAY] Manche configurée : {customRound.TotalCombatants} ennemis au total ({customRound.MaxConcurrentFighters} max simultanés)." );
}
}