BaddieWardrobe.cs
using Sandbox;
using System.Linq;
namespace PaintballBaddies;

/// <summary>Complete Hi3D visuals using the existing native player and opponent animations.</summary>
public sealed class BaddieWardrobe : Component
{
    private SkinnedModelRenderer driver;
    private Model originalDriver;
    private int selected=-1;
    private string requestedPath;
    private string requestedClothing;
    private ClothingContainer clothing;
    public bool Ready {get;private set;}
    public SkinnedModelRenderer Visual {get;private set;}
    public string[] Models {get;private set;}=System.Array.Empty<string>();
    public void Present(SkinnedModelRenderer animationDriver,int index,bool visible)
    {
        if(!animationDriver.IsValid())return;
        if(driver!=animationDriver){Clear();driver=animationDriver;originalDriver=driver.Model;}
        bool opponent=GameObject.Parent.Components.Get<CitizenOpponentPresentation>().IsValid();
        string path=visible ? BaddieWardrobeCatalog.ModelPath(index,opponent) : null;
        var items=visible ? BaddieWardrobeCatalog.ActiveLook(index)?.ClothingItems : null;
        string clothingKey=items is null ? "" : string.Join("|",items.Select(c=>c?.Asset ?? "missing"));
        if(visible && (selected!=index || requestedPath!=path || requestedClothing!=clothingKey))Build(index,opponent,path,clothingKey);
        if(!visible && selected>=0){Clear();if(originalDriver.IsValid())driver.Model=originalDriver;}
    }
    private void Build(int index,bool opponent,string path,string clothingKey)
    {
        Clear();
        // Keep the fitted joints, with skin weights matched to their new elbow
        // positions so bent arms do not pinch into angular wedges.
        var animation=Model.Load(path);
        if(!animation.IsValid() || animation.IsError)
            animation=Model.Load(BaddieWardrobeCatalog.DefaultModel(index,opponent));
        if(!animation.IsValid() || animation.IsError)return;
        // Use the fitted skeleton's bind pose while retaining every existing
        // animation graph parameter and native hand/foot IK constraint. Contact
        // and vault clips retain this character's joint lengths and floor contact.
        if(driver.Model!=animation)driver.Model=animation;
        var items=BaddieWardrobeCatalog.ActiveLook(index)?.ClothingItems;
        var garments=items?.Select(c=>string.IsNullOrWhiteSpace(c?.Asset) ? null : ResourceLibrary.Get<Clothing>(c.Asset)).ToArray();
        if(garments is {Length:>0} && garments.All(c=>c is not null))
        {
            clothing=new ClothingContainer {PrefersHuman=true};
            foreach(var item in garments)clothing.Add(item);
            // Native clothing owns layering, bodygroups and bone-merged children.
            // Garments must be pre-fitted; this is not an automatic body reshaper.
            clothing.Apply(driver);
        }
        Visual=driver;
        Models=new[]{animation.Name};selected=index;requestedPath=path;requestedClothing=clothingKey;Ready=true;
    }
    private void Clear()
    {
        if(clothing is not null && driver.IsValid())clothing.Reset(driver);
        clothing=null;requestedClothing=null;
        Visual=null;
        Ready=false;selected=-1;requestedPath=null;Models=System.Array.Empty<string>();
    }
    // The hierarchy owns the garments. Resetting the clothing during teardown
    // would write bodygroups/morphs on a renderer already being destroyed.
    protected override void OnDestroy()
    {
        clothing=null;driver=null;originalDriver=null;Visual=null;
        Ready=false;Models=System.Array.Empty<string>();
    }
}