Core/ChaserGameplayPreview.cs
namespace Prototype;
/// <summary>Reversible visual experiment on the first Chaser of each wave.</summary>
public sealed class ChaserGameplayPreview : Component
{
    private GameObject _visual;
    private SkinnedModelRenderer _renderer;
    private ModelHitboxes _hitboxes;
    private float _playedAttackAt = -100f;
    private int _playedDamage;
    private Vector3 _previous;
    private string _clip;
    private float _hitUntil;
    private int _damage;
    private float _originalEnd;
    private bool _rejected;
    protected override void OnStart()
    {
        _previous = WorldPosition;
        _originalEnd = Components.Get<CapsuleCollider>()?.End.z ?? 0f;
    }
    protected override void OnUpdate()
    {
        var brain = Components.Get<EnemyBrain>();
        if ( !brain.IsValid() ) return;
        bool active = PrototypeGame.Instance?.ReviewChaserInGame == true;
        if ( active && !_visual.IsValid() )
        {
            if ( _rejected ) return;
            var model = Model.Load( "models/review/chaser_stylized_v001/chaser.vmdl" );
            if ( model is null || model.Bounds.Size.z < 60 || model.Bounds.Size.z > 90 || model.Bounds.Size.x > 85 || model.Bounds.Size.y > 85 || model.BoneCount != 69 )
            {
                _rejected = true;
                Log.Error( "CHASER_GAMEPLAY: candidate rejected by bounds/bone check; keeping original visual" );
                return;
            }
            _visual = new GameObject( true, "Chaser candidate v004" );
            _visual.NetworkMode = NetworkMode.Never;
            _visual.SetParent( GameObject, false );
            _visual.LocalPosition = Vector3.Down * (27f * brain.Spec.Scale + 2f);
            _renderer = _visual.Components.Create<SkinnedModelRenderer>();
            _renderer.Model = model; _renderer.UseAnimGraph = false;
            _renderer.Sequence.Blending = true;
            _visual.Tags.Add( "enemy" );
            _hitboxes = _visual.Components.Create<ModelHitboxes>();
            _hitboxes.Renderer = _renderer;
            _hitboxes.Target = GameObject;
        }
        if ( _hitboxes.IsValid() ) _hitboxes.Enabled = active && brain.Vitality?.IsDead != true;
        if ( active && _hitboxes.IsValid() ) GameObject.Tags.Add( "enemy_model_proxy" );
        else GameObject.Tags.Remove( "enemy_model_proxy" );
        if ( brain.Vitality?.IsDead == true )
        {
            var deadCapsule = Components.Get<CapsuleCollider>();
            if ( deadCapsule.IsValid() ) deadCapsule.Enabled = false;
        }
        if ( _visual.IsValid() )
        {
            _visual.Enabled = active;
            _visual.LocalPosition = Vector3.Down * (27f * brain.Spec.Scale + 2f);
        }
        foreach ( var child in GameObject.Children )
            if ( child.Name == "Chaser Visual" )
            {
                var oldRenderer = child.Components.Get<ModelRenderer>();
                if ( oldRenderer is not null ) oldRenderer.Enabled = !active;
                var oldAnimator = child.Components.Get<EnemyVisualAnimator>();
                if ( oldAnimator is not null ) oldAnimator.Enabled = !active;
                child.Enabled = !active;
            }
        var capsule = Components.Get<CapsuleCollider>();
        if ( capsule.IsValid() && brain.Vitality?.IsDead != true ) capsule.End = Vector3.Up * (active ? 75.4752f - (27f * brain.Spec.Scale + 2f) - capsule.Radius : _originalEnd);
        var speed = (WorldPosition - _previous).WithZ(0).Length / System.MathF.Max(Time.Delta, .001f);
        _previous = WorldPosition;
        if ( !active || _renderer is null ) return;
        if ( brain.DamageSequence != _damage ) { _damage = brain.DamageSequence; _hitUntil = Time.Now + .6f; }
        var clip = brain.Vitality?.IsDead == true ? "death" : Time.Now < _hitUntil ? "hit" : Time.Now - brain.LastAttackAt < .9f ? "attack" : speed > 10 ? "run" : "idle";
        var repeatedAttack = (clip is "attack" or "cast") && _playedAttackAt != brain.LastAttackAt;
        var repeatedHit = clip == "hit" && _playedDamage != brain.DamageSequence;
        if ( clip == _clip && !repeatedAttack && !repeatedHit ) return;
        _playedAttackAt = brain.LastAttackAt;
        _playedDamage = brain.DamageSequence;
        _clip = clip; _renderer.Sequence.Name = clip; _renderer.Sequence.Time = 0;
        _renderer.Sequence.PlaybackRate = clip == "attack"
            ? System.MathF.Max( 1f, _renderer.Sequence.Duration / System.MathF.Max( .1f, System.MathF.Min( .9f, brain.Spec.AttackCooldown ) ) )
            : 1f;
        _renderer.Sequence.Looping = clip is "idle" or "run";
    }
}