UI/debug_overlay.razor
@using System;
@using System.Collections.Generic;
@using System.Linq;
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@if ( !_isOpen ) return;
<root>
<div class="header-bar">
<div class="title-group">
<span class="main-title">Gladiator Debugger</span>
<span class="sub-title">Cible : @(CurrentFighter?.GameObject.Name ?? "Aucune")</span>
</div>
<div class="target-nav">
<button @onclick=@CyclePreviousTarget>◄</button>
<button @onclick=@CycleNextTarget>►</button>
</div>
</div>
@if ( CurrentFighter != null )
{
<div class="section-header">Diagnostic Animgraph</div>
<DataRow Label="AnimParam [attack]"
Value="@(AnimAttackParam ? "TRUE (EN COURS)" : "FALSE")"
ValueColor="@(AnimAttackParam ? "#f59e0b" : "#ef4444")" />
<DataRow Label="AnimParam [attack_index]"
Value="@AnimAttackIndex.ToString()" />
<DataRow Label="AnimParam [is_kicking]"
Value="@(AnimKickParam ? "TRUE" : "FALSE")"
ValueColor="@(AnimKickParam ? "#f59e0b" : "#ef4444")" />
<div class="section-header">État Fighter</div>
<DataRow Label="Points de Vie"
Value="@($"{CurrentFighter.CurrentHealth:F0} / {CurrentFighter.MaxHealth:F0}")"
ValueColor="@GetHealthColor()" />
<DataRow Label="Statut"
Value="@CurrentFighter.Status.ToString()" />
<DataRow Label="Parade Active"
Value="@(CurrentFighter.IsParrying ? "OUI" : "NON")"
ValueColor="@(CurrentFighter.IsParrying ? "#22c55e" : "#94a3b8")" />
@if ( Movement != null )
{
<div class="section-header">Locomotion</div>
<DataRow Label="Au Sol (Grounded)"
Value="@(Movement.Controller?.IsOnGround == true ? "OUI" : "NON")" />
<DataRow Label="Vitesse"
Value="@($"{GetHorizontalSpeed():F1} u/s")" />
}
}
</root>
@code {
[Property] public string ToggleAction { get; set; } = "Score"; // Touche TAB
private bool _isOpen = false;
private List<Fighter> _allFighters = new();
private int _targetIndex = 0;
private Fighter CurrentFighter => (_allFighters.Count > 0 && _targetIndex >= 0 && _targetIndex < _allFighters.Count)
? _allFighters[_targetIndex]
: null;
private FighterMovement Movement => CurrentFighter?.Components.Get<FighterMovement>();
private SkinnedModelRenderer MainRenderer => CurrentFighter?.Components.GetInChildren<SkinnedModelRenderer>();
private bool AnimAttackParam => MainRenderer?.GetBool( "attack" ) ?? false;
private int AnimAttackIndex => MainRenderer?.GetInt( "attack_index" ) ?? 0;
private bool AnimKickParam => MainRenderer?.GetBool( "is_kicking" ) ?? false;
protected override void OnUpdate()
{
// 1. Gestion de l'ouverture / fermeture via le gestionnaire d'état
if ( Input.Pressed( ToggleAction ) )
{
SetOpenState( !_isOpen );
}
if ( !_isOpen ) return;
// 2. Scan des combattants présents
_allFighters = Scene.GetAllComponents<Fighter>().ToList();
if ( _targetIndex >= _allFighters.Count )
_targetIndex = Math.Max( 0, _allFighters.Count - 1 );
}
private void SetOpenState( bool open )
{
_isOpen = open;
// Enregistrement auprès du gestionnaire central
if ( _isOpen )
UIStateManager.RequestFocus( this );
else
UIStateManager.ReleaseFocus( this );
}
protected override void OnDestroy()
{
// Sécurité : si le composant est détruit pendant qu'il est ouvert, il libère son focus
if ( _isOpen )
UIStateManager.ReleaseFocus( this );
}
private void CycleNextTarget()
{
if ( _allFighters.Count == 0 ) return;
_targetIndex = (_targetIndex + 1) % _allFighters.Count;
}
private void CyclePreviousTarget()
{
if ( _allFighters.Count == 0 ) return;
_targetIndex = (_targetIndex - 1 + _allFighters.Count) % _allFighters.Count;
}
private float GetHorizontalSpeed() => Movement?.Controller?.Velocity.WithZ( 0 ).Length ?? 0f;
private string GetHealthColor()
{
if ( CurrentFighter == null ) return "#ffffff";
var ratio = CurrentFighter.CurrentHealth / CurrentFighter.MaxHealth;
return ratio > 0.5f ? "#22c55e" : (ratio > 0.25f ? "#f59e0b" : "#ef4444");
}
protected override int BuildHash()
{
return HashCode.Combine(
_isOpen,
AnimAttackParam,
AnimKickParam,
CurrentFighter?.CurrentHealth,
CurrentFighter?.Status,
_targetIndex
);
}
}