Player/DasherBodyVisual.cs
using System.Linq;

namespace Prototype;

/// <summary>Shared rig for first-person arms and the visible multiplayer body.</summary>
public sealed class DasherBodyVisual : Component
{
    private GameObject _body, _weapon;
    private SkinnedModelRenderer _renderer;
    private ModelRenderer _weaponRenderer;
    private string _clip, _weaponId;
    private bool _firstPerson;
    public bool Ready => _renderer.IsValid() && _weaponRenderer.IsValid();
    public Vector3 MuzzlePosition
    {
        get
        {
            UpdateWeaponPose();
            return _weapon.IsValid() ? _weapon.WorldPosition + _weapon.WorldRotation.Forward * (_weaponId == "scatter" ? 24.66f : 27.64f) + _weapon.WorldRotation.Up * (_weaponId == "scatter" ? 4.91f : 4.72f) : WorldPosition;
        }
    }

    protected override void OnUpdate() => PrepareView();

    public void PrepareView()
    {
        // Networked component must be unique. Old clients/hotload can retain a
        // locally-created copy from the previous OnStart setup.
        var primary = Components.GetAll<DasherBodyVisual>().FirstOrDefault();
        if (primary != this)
        {
            if (_body.IsValid()) _body.Enabled = false;
            if (_weapon.IsValid()) _weapon.Enabled = false;
            return;
        }
        var player = Components.Get<FpsPlayer>();
        if ( !player.IsValid() ) return;
        // Hide legacy bodies left by hot reload or older replicated hierarchies.
        foreach (var child in GameObject.Children)
            if (child.Name == "DASHER Visual" || child.Name == "THE NOMAD Visual"
                || (child != _body && (child.Name == "Dasher View Arms" || child.Name == "Dasher Skinned Body"))) child.Enabled = false;
        var viewer = PrototypeGame.Instance?.LocalPlayer;
        var watched = viewer?.IsSpectating == true && viewer.SpectatorTarget == player;
        var firstPerson = watched || (viewer == player && !player.ThirdPersonView);
        player.UpdateLegacyVisualVisibility(firstPerson, Ready);
        if ( !_renderer.IsValid() || _firstPerson != firstPerson )
        {
            var model = Model.Load( firstPerson ? "models/review/dasher_v003/arms.vmdl" : "models/review/dasher_v003/body.vmdl" );
            if ( model is null || model.BoneCount < (firstPerson ? 10 : 30) ) return;
            if ( _body.IsValid() ) _body.Destroy();
            _firstPerson = firstPerson;
            _body = new GameObject( false, firstPerson ? "Dasher View Arms" : "Dasher Skinned Body" );
            _body.NetworkMode = NetworkMode.Never;
            _body.SetParent( GameObject, false );
            _renderer = _body.Components.Create<SkinnedModelRenderer>();
            _renderer.Model = model; _renderer.UseAnimGraph = false;
            _renderer.RenderOptions.Game = !firstPerson; _renderer.RenderOptions.Overlay = firstPerson;
            _weapon = new GameObject( true, "Dasher Held Weapon" ); _weapon.SetParent( _body, false );
            _weapon.NetworkMode = NetworkMode.Never;
            _weaponRenderer = _weapon.Components.Create<ModelRenderer>();
            _weaponRenderer.RenderOptions.Game = !firstPerson; _weaponRenderer.RenderOptions.Overlay = firstPerson;
            _body.Enabled = true;
            _clip = null; _weaponId = null;
            foreach ( var child in GameObject.Children )
                if ( child.Name == "DASHER Visual" ) child.Enabled = false;
        }
        if ( _weaponId != player.BodyWeaponId )
        {
            _weaponId = player.BodyWeaponId;
            _weaponRenderer.Model = Model.Load( _weaponId == "scatter" ? "models/review/dasher_v003/scatter.vmdl" : "models/review/dasher_v003/rifle.vmdl" );
        }
        var dead = player.Vitality?.IsDead == true || player.BodyAnimation == "death";
        _body.Enabled = !firstPerson || !dead;
        _weapon.Enabled = !dead;
        var clip = dead ? "death" : player.WeaponSwitchProgress > 0f ? "swap" : player.BodyAnimation ?? "idle";
        // Disabled view arms have no scene object; do not write sequence time
        // during death/model creation before the renderer has been initialized.
        if ( !_body.Enabled || _renderer.SceneObject is null ) return;
        if ( clip != _clip )
        {
            _clip = clip; _renderer.Sequence.Name = clip; _renderer.Sequence.Time = 0;
            _renderer.Sequence.Looping = clip is "idle" or "run" or "walk";
            _renderer.PlaybackRate = clip.StartsWith( "dash" ) ? (17f / 30f) / player.DashDuration : 1f;
        }
        if ( clip == "reload" ) _renderer.Sequence.Time = player.BodyReloadProgress * (53f / 30f);
        if ( clip == "swap" ) _renderer.Sequence.Time = player.WeaponSwitchProgress * (16f / 30f);
    }

    protected override void OnPreRender()
    {
        PrepareView();
        UpdateWeaponPose();
    }

    private void UpdateWeaponPose()
    {
        if ( !Ready || _renderer.SceneObject is null ) return;
        var player = Components.Get<FpsPlayer>();
        // Bone poses belong to the renderer's sampled transform, which can lag the
        // camera root. Convert to model space before applying this frame's root.
        var sampledRoot = _renderer.SceneObject.Transform;
        var hasGrip = _renderer.TryGetBoneTransform( "attach_right_grip", out var grip );
        var localGrip = hasGrip ? sampledRoot.ToLocal( grip ) : _renderer.Model.GetBoneTransform( "attach_right_grip" );
        if ( _firstPerson )
        {
            var rotation = player.ViewRigRotation;
            _body.WorldRotation = rotation;
            _body.WorldPosition = player.EyePosition + player.ViewRigOffset - rotation.Up * 59f + rotation.Forward * 20f;
        }
        if ( _weapon.Parent != _body ) _weapon.SetParent( _body, false );
        _weapon.LocalPosition = localGrip.Position;
        _weapon.LocalRotation = localGrip.Rotation * _renderer.Model.GetBoneTransform( "attach_right_grip" ).Rotation.Inverse;
    }

    protected override void OnDestroy()
    {
        if ( _weapon.IsValid() ) _weapon.Destroy();
        if ( _body.IsValid() ) _body.Destroy();
    }
}