k/Extensions/ClothingContainerExtensions.cs
// author: LIMESTA
// https://sbox.game/f/code/335/1
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sandbox.k.Extensions;
public static class ClothingContainerExtensions
{
/// <summary>
/// Not working and I'm lazy to see why
/// Dress a skinned model renderer with an outfit
/// </summary>
public static async Task ApplyAsync( this ClothingContainer container, SkinnedModelRenderer body )
{
var isHuman = DetermineHuman( body );
// remove out outfit
body.Reset();
container.Normalize();
// apply indicentals
body.Set( "scale_height", container.Height );
// Clean the clothing. Remove any invalid items, any items with broken models
// any items that can't be worn with other items.
var set = (await GameTask.WhenAll(
container.Clothing?.Select( async x => new { Item = x, IsValid = await IsValidClothing( x, isHuman ) } )
)).Where(x => x.IsValid)
.Select(x => x.Item)
.ToList();
set ??= new List<ClothingContainer.ClothingEntry>();
TagSet tags = new();
// apply alternate human skin, if we have one
if ( isHuman )
{
tags.Add( "human" );
var humanskin = set.Where( x => x.Clothing.HasHumanSkin ).FirstOrDefault();
if ( humanskin is not null && await Model.LoadAsync( humanskin.Clothing.HumanSkinModel ) is Model model && model.IsValid() )
{
body.Model = model;
tags.Add( humanskin.Clothing.HumanSkinTags );
body.BodyGroups = humanskin.Clothing.HumanSkinBodyGroups;
body.MaterialGroup = humanskin.Clothing.HumanSkinMaterialGroup;
}
else
{
// restore to default, somehow?
//body.Model = Model.Load( humanskin.Clothing.HumanSkinModel );
}
}
body.SetMaterialOverride( null, "skin" );
body.SetMaterialOverride( null, "eyes" );
var skinMaterial = set.Select( x => x.Clothing.SkinMaterial ).Where( x => !string.IsNullOrWhiteSpace( x ) ).Select( x => Material.Load( x ) ).FirstOrDefault();
var eyesMaterial = set.Select( x => x.Clothing.EyesMaterial ).Where( x => !string.IsNullOrWhiteSpace( x ) ).Select( x => Material.Load( x )).FirstOrDefault();
if ( !isHuman )
{
body.SetMaterialOverride( skinMaterial, "skin" );
body.SetMaterialOverride( eyesMaterial, "eyes" );
}
// Create clothes models
foreach ( var entry in set )
{
var c = entry.Clothing;
var modelPath = c.GetModel( set.Select( x => x.Clothing ).Except( new[] { c } ), tags );
if ( string.IsNullOrEmpty( modelPath ) || !string.IsNullOrEmpty( c.SkinMaterial ) )
continue;
var model = await Model.LoadAsync( modelPath );
if ( !model.IsValid() || model.IsError )
continue;
var go = new GameObject( false, $"Clothing - {c.ResourceName}" );
go.Parent = body.GameObject;
go.Tags.Add( "clothing" );
var r = go.Components.Create<SkinnedModelRenderer>();
r.Model = model;
r.BoneMergeTarget = body;
if ( !isHuman )
{
if ( skinMaterial is not null ) r.SetMaterialOverride( skinMaterial, "skin" );
if ( eyesMaterial is not null ) r.SetMaterialOverride( eyesMaterial, "eyes" );
}
if ( !string.IsNullOrEmpty( c.MaterialGroup ) )
r.MaterialGroup = c.MaterialGroup;
if ( c.AllowTintSelect )
{
var tintValue = entry.Tint?.Clamp( 0, 1 ) ?? c.TintDefault;
var tintColor = c.TintSelection.Evaluate( tintValue );
r.Tint = tintColor;
}
go.Enabled = true;
}
// Set body groups
foreach ( var (name, value) in container.GetBodyGroups( set.Select( x => x.Clothing ) ) )
{
if ( value == 0 ) continue;
body.SetBodyGroup( name, value );
}
}
private static bool DetermineHuman( SkinnedModelRenderer b, bool defaultValue = false )
{
if ( b is null ) return defaultValue;
if ( b.Model is null ) return defaultValue;
if ( b.Model is null ) return defaultValue;
if ( b.Model.Name.Contains( "citizen.vmdl", System.StringComparison.OrdinalIgnoreCase ) ) return false;
return true;
}
private static async Task<bool> IsValidModel( string modelName )
{
if ( string.IsNullOrWhiteSpace( modelName ) )
return false;
var model = await Model.LoadAsync( modelName );
if ( !model.IsValid() ) return false;
if ( model.IsError ) return false;
return true;
}
private static async Task<bool> IsValidClothing( ClothingContainer.ClothingEntry e, bool targetIsHuman )
{
if ( e is null ) return false;
if ( e.Clothing is null ) return false;
if ( targetIsHuman && e.Clothing.HasHumanSkin ) return true;
var model = e.Clothing.Model;
if ( targetIsHuman )
{
model = e.Clothing.HumanAltModel;
// If we have a citizen model, but not a human model, make clothing invalid
if ( string.IsNullOrEmpty( model ) && !string.IsNullOrEmpty( e.Clothing.Model ) )
return false;
}
if ( string.IsNullOrEmpty( model ) )
return true;
if ( ! await IsValidModel( model ) )
{
Log.Warning( $"Clothing model '{model}' in {e.Clothing} is invalid, removing" );
return false;
}
return true;
}
}