ai/ai_brain.cs
// AiBrain.cs
using System;
using System.Collections.Generic;
using Sandbox;

public sealed class AiBrain : Component
{
    [Property, Group( "Debug & Test" )]
    [Description( "Si coché, le bot s'immobilise et n'attaque plus. Il sert de mannequin d'entraînement pour tester les coups, l'équilibre, le knockdown et les armes." )]
    public bool IsPacifist { get; set; } = false;

    public Fighter Fighter { get; private set; }
    public FighterMovement Movement { get; private set; }
    public FighterCombat Combat { get; private set; }
    public AiBlackboard Blackboard { get; private set; }
    public NavMeshAgent Agent { get; private set; }

    public AiState ActiveState { get; private set; }
    public AiState PreviousState { get; private set; }
    public string ActiveStateName => ActiveState?.StateName ?? "None";
    public TimeSince TimeInState { get; private set; } = 0f;

    private readonly Dictionary<Type, AiState> _states = new();
    private FighterStatus _lastFighterStatus = FighterStatus.Active;

    protected override void OnAwake()
    {
        Fighter = Components.Get<Fighter>();
        Movement = Components.Get<FighterMovement>();
        Combat = Components.Get<FighterCombat>();
        Blackboard = Components.Get<AiBlackboard>();
        Agent = Components.Get<NavMeshAgent>();
    }

    protected override void OnStart()
    {
        Register( new AiEngageState() );
        Register( new AiSeekWeaponState() );

        TransitionTo<AiEngageState>();
    }

    protected override void OnUpdate()
    {
        if ( Fighter == null ) return;

        // 1. Verrou d'incapacitation : arrêt moteur si étourdi, au sol ou mort
        if ( Fighter.Status != FighterStatus.Active )
        {
            if ( Agent != null && Agent.IsValid && Agent.Enabled )
                Agent.Stop();

            _lastFighterStatus = Fighter.Status;
            return;
        }

        // 2. Détection de reprise de conscience (relèvement après knockdown ou fin de stagger)
        if ( _lastFighterStatus != FighterStatus.Active )
        {
            OnFighterRecovered();
            _lastFighterStatus = FighterStatus.Active;
        }

        // 3. Mode Pacifiste / Mannequin d'entraînement
        if ( IsPacifist )
        {
            if ( Agent != null && Agent.IsValid && Agent.Enabled )
                Agent.Stop();

            if ( Movement != null )
                Movement.WishVelocity = Vector3.Zero;

            return;
        }

        // 4. Exécution normale du comportement de combat
        ActiveState?.OnUpdate();
    }

    private void OnFighterRecovered()
    {
        ActiveState?.OnExit();
        TimeInState = 0f;
        ActiveState?.OnEnter();
    }

    public void TransitionTo<T>() where T : AiState
    {
        var type = typeof( T );
        if ( !_states.TryGetValue( type, out var nextState ) ) return;
        if ( ActiveState == nextState ) return;

        ActiveState?.OnExit();
        PreviousState = ActiveState;
        ActiveState = nextState;
        TimeInState = 0f;
        ActiveState.OnEnter();
    }

    private void Register( AiState state )
    {
        state.Bind( this );
        _states[state.GetType()] = state;
    }
}