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;
}
}