Code/Demos/TarkovInventory/AvatarView.cs
using System.Collections.Generic;

namespace Sandbox.TarkovInventory;

// owns a Scene from the authored .scene asset; Sync(Loadout) drives the Dresser with .clothing resources (bone-merge path); owning the scene (not ScenePath) is the hot-swap-safe ref path (engine-fact-scenepanel-renderscene-setter), created with WantsSystemScene = false.
public sealed class AvatarView
{
    // The authored scene asset, relative to Assets/. CONFIRM this matches Zack's save path.
    const string ScenePathConst = "scenes/avatar.scene";

    // Slot id -> clothing resource path. Mirrors AvatarOutfit's demo mapping; only the
    // helmet is mapped today. CONFIRM the m1helmet clothing path resolves in-editor.
    readonly AvatarOutfit _outfit = new( new Dictionary<string, string>
    {
        ["helmet"] = "m1helmet/m1helmet.clothing",
    } );

    Scene? _scene;
    IReadOnlySet<string>? _applied;   // last-applied clothing set; skip work when unchanged

    // Lazily load the authored scene into an owned Scene (ctors do not run on hotload, the
    // same idiom as TarkovStashUI.Stash()/Gear(); rebuild if the scene was torn down).
    public Scene Scene
    {
        get
        {
            if ( _scene is not null && _scene.IsValid() ) return _scene;
            _scene = new Scene { WantsSystemScene = false };
            var file = ResourceLibrary.Get<SceneFile>( ScenePathConst );
            if ( file is not null ) _scene.Load( file );
            _applied = null;   // force the next Sync to (re)dress against the fresh scene
            return _scene;
        }
    }

    // Dress the citizen to match the equip state. Cheap no-op when the resolved clothing set
    // is unchanged, so it is safe to call on every Rebuild().
    public void Sync( Loadout gear )
    {
        var want = _outfit.Resolve( gear );
        if ( _applied is not null && want.SetEquals( _applied ) ) return;

        var dresser = FindDresser();
        if ( dresser is null ) return;

        dresser.Source = Dresser.ClothingSource.Manual;
        var entries = new List<ClothingContainer.ClothingEntry>();
        foreach ( var path in want )
        {
            var clothing = ResourceLibrary.Get<Clothing>( path );
            if ( clothing is not null ) entries.Add( new ClothingContainer.ClothingEntry( clothing ) );
        }
        dresser.Clothing = entries;
        _ = dresser.Apply();   // async dress; helmet appears (or clears) on the citizen body

        _applied = want;
    }

    // The citizen's Dresser lives on the body root (e.g. "Terry"); find it by type so the
    // exact GameObject name does not matter. Include disabled in case it starts inactive.
    Dresser? FindDresser()
    {
        foreach ( var go in Scene.GetAllObjects( true ) )
        {
            var d = go.Components.Get<Dresser>( true );
            if ( d is not null ) return d;
        }
        return null;
    }
}