//=============================================================================
// KFCharacterInfo_Human
//=============================================================================
// Class Description
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//  - Author 3/19/2014
//=============================================================================

class KFCharacterInfo_Human extends KFCharacterInfoBase
	native(Pawn)
	implements(KFUnlockableAsset)
	hidecategories(Object);

`include(KFGame\KFGameAnalytics.uci);
`include(KFGame\KFMatchStats.uci);

/************************************************************************/
/*  Identification													    */
/************************************************************************/

var() const int UnlockAssetID;

/** Whether these are female characters */
var() bool bIsFemale;

/************************************************************************/
/*  3P Mesh Info							                            */
/************************************************************************/

/** The material ID for the skin in the mesh */
var(ThirdPerson) int HeadMaterialID;
var(ThirdPerson) int BodyMaterialID;

enum ECosmeticType
{
	ECosmeticType_Head,
	ECosmeticType_Body,
	ECosmeticType_Attachment,
	ECosmeticType_Arms
};

struct native SkinVariant
{
	var() const int UnlockAssetID;
	/** The path to this skins package and texture */
	var() Texture UITexture;
	/** The material this outfit can use */
 	var MaterialInstance Skin;
	var() string SkinName;
	/** The first person material of this outfit */
	var MaterialInstance Skin1p;
	var() string Skin1pName;
};

struct native OutfitVariants
{
	//var() const int UnlockAssetID;
	/** The path to this skins package and texture */
	var() Texture UITexture;
	/** Hard reference to the mesh, async load. */
	var edithide transient SkeletalMesh Mesh;
	/** Outfit mesh name. Must be of form PackageName.MeshName */
	var() string MeshName;
	/** Reference to the different skin variants for a particular outfit mesh */
	var() array<SkinVariant> SkinVariations;
};

/** List of booleans that will effect which items can be attached with the current attachment */
struct native AttachmentOverrideList
{
	var() bool bHat;
	var() bool bFace;
	var() bool bEyes;
	var() bool bJaw;
	var() bool bArmband;
	var() bool bBackpack;

	/** List of cosmetic indices that this attachment will detach, if they are currently attached to a player */
	var array<byte> SpecialOverrideIds;
};

struct native AttachmentVariants
{
	var() KFCharacterAttachment AttachmentItem;

	var() const int UnlockAssetID;
	/** The path to this skins package and texture */
	var Texture UITexture;
	/** If set enables the material mask parameter on the assigned head variant */
	var() bool bMaskHeadMesh;
	/** Hard reference to the mesh, async load. */
	var edithide transient StaticMesh MeshStatic;
	/** Hard reference to the mesh, async load. */
	var edithide transient SkeletalMesh MeshSkeletal;
	/** Attachment mesh name. Must be of form PackageName.MeshName */
	var() string MeshName;
	/** Name of the socket that it attaches to. The socket MUST exist in the body mesh for static mesh
		attachments to work. SocketName is also used to resolve conflicts - when more than one attachment
		tries to attach to the same socket, it will replace the previously existing attachment. It will keep
		both if there is no conflict. NOTE: Skeletal meshes do not require sockets for attachment, but the socket name
		can still be used for conflit resolution. */
	var name SocketName;
	/** Hard reference to the mesh, async load. */
	var edithide transient StaticMesh MeshStatic1p;
	/** Hard reference to the mesh, async load. */
	var edithide transient SkeletalMesh MeshSkeletal1p;
	/** Mesh name for 1p attachmentt */
	var() string MeshName1p;
	/** Name of the socket that the 1p mesh attaches to. If bIsSkeletalAttachment is true, then the mesh will attach
	    to the ParentAnimComponent. */
	var() name SocketName1p;
	/** Translation relative to given socket (for additional control) */
	var() vector RelativeTranslation;
	/** Rotation relative to given socket (for additional control) */
	var() rotator RelativeRotation;
	/** Scale relative to given socket (for additional control) */
	var() vector RelativeScale;
	/** Distance at which the attachment will be hidden (distance culled). If 0, it is never culled */
	var float MaxDrawDistance;
	/** Material ID used for skin variations for this attachment (default is 0) */
	var int SkinMaterialID;
	/** Reference to the different skin variants for a particular attachment mesh */
	var array<SkinVariant> SkinVariations;
	/** List of sockets that this attachment will detach, if they are currently attached to a player */
	var AttachmentOverrideList OverrideList;

	/** List of cosmetic indices that this attachment will detach, if they are currently attached to a player */
	var array<byte> SpecialOverrideIds;

	var() array<KFCharacterAttachment> SpecialOverrideAttachments;

	structdefaultproperties
	{
		RelativeScale=(X=1.f,Y=1.f,Z=1.f)
	}
};

/**
	Whether to enable Character Customization. If TRUE, it uses the settings below
	to assemble the character, otherwise it uses the CharacterMesh
*/

/** The package key for async loading purposes. Console only. */
var(ThirdPerson) string CosmeticsPackageKey;
/** The outfit variants for a character. Used for Character Customization */
var(ThirdPerson) const array<OutfitVariants> BodyVariants;
/** The head variants for a character. Used for Character Customization */
var(ThirdPerson) const array<OutfitVariants> HeadVariants;
/** Various cosmetic variants for a character. Used for Character Customization */
var(ThirdPerson) const array<AttachmentVariants> CosmeticVariants;

/************************************************************************/
/*  1P Character Info                                                   */
/************************************************************************/

struct native FirstPersonArmVariants
{
	/** Arms mesh. */
	var edithide transient SkeletalMesh Mesh;
	/** Mesh name. Must be of form PackageName.MeshName */
	var() string MeshName;
	/**	Reference to the different skin variants for a particular arms mesh */
 	var edithide init array<MaterialInstance> SkinVariants;
	var() array<string> SkinVariantsName;
};

/** The outfit variants for a character. Used for Character Customization */
var(FirstPerson) array<FirstPersonArmVariants> CharacterArmVariants;

/** Package to load to find the arm mesh for this char. */
var(FirstPerson) string	ArmMeshPackageName;

/** Name of mesh within ArmMeshPackageName to use for arms. */
var(FirstPerson) SkeletalMesh	ArmMesh;

/** Package that contains team-skin materials for first-person arms. */
var(FirstPerson) string			ArmSkinPackageName;

/************************************************************************/
/*  Favorite Weapons (for dialog)                                       */
/************************************************************************/

// make sure this matches NUM_FAVE_WEAPS in KFDialogManager
const NUM_FAVE_WEAPS = 8;
var(FaveWeapons) editconst name FavoriteWeaponClassNames[NUM_FAVE_WEAPS];
var(FaveWeapons) editoronly class<KFWeaponDefinition> FavoriteWeaponClassDefs[NUM_FAVE_WEAPS]<AllowAbstract>;

/************************************************************************/
/*  Emote AnimSets                             					        */
/************************************************************************/
var Animset EmoteAnimset;


/************************************************************************/
/*  Native Functions												    */
/************************************************************************/

cpptext
{
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent);

	// set FavoriteWeaponClassNames from FavoriteWeaponClassDefs
	void SetFavoriteWeaponClassNames();

	virtual void AddReferencedObjects(TArray<UObject*>& ObjectArray);
}

/************************************************************************/
/*  Script Functions												    */
/************************************************************************/

/** Implements KFUnlockableAsset */
function int GetAssetId()
{
	return UnlockAssetID;
}

function string GetMesh(const out AttachmentVariants Variant)
{
	if (Len(Variant.MeshName) > 0)
	{
		return Variant.MeshName;
	}
	else
	{
		return Variant.AttachmentItem.MeshName;
	}
}

function string GetMeshByIndex(int index)
{
	return GetMesh(CosmeticVariants[index]);
}

function string Get1pMesh(const out AttachmentVariants Variant)
{
	if (Len(Variant.MeshName1p) > 0)
	{
		return Variant.MeshName1p;
	}
	else
	{
		return Variant.AttachmentItem.MeshName1p;
	}
}

function string Get1pMeshByIndex(int index)
{
	return Get1pMesh(CosmeticVariants[index]);
}

/** Return the 1P arm skeletal mesh representation for the class */
function SkeletalMesh GetFirstPersonArms()
{
	if (ArmMesh == None)
	{
		`warn("Unable to load first person arms");
	}

	return ArmMesh;
}

/** Return the texture portrait stored for this character */
/*function Texture GetCharPortrait(int TeamNum)
{
	return (TeamNum < DefaultTeamHeadPortrait.length) ? DefaultTeamHeadPortrait[TeamNum] : DefaultHeadPortrait;
}*/

function int GetFavoriteWeaponIndexOf( Weapon W )
{
	local int i;

    for( i = 0; i < NUM_FAVE_WEAPS; ++i )
    {
       if( W.Class.Name == FavoriteWeaponClassNames[i] )
       {
           return i;
       }
    }

    return -1;
}

/** Sets misc. properties from the character info */
simulated function SetCharacterFromArch( KFPawn KFP, optional KFPlayerReplicationInfo KFPRI )
{
   	super.SetCharacterFromArch( KFP, KFPRI );

	if ( KFPRI == none )
	{
        `Warn("Does not have a KFPRI" @ Self);
	 	return;
	}

   	// a little hacky, relies on presumption that enum vals 0-3 are male, 4-8 are female
	if ( bIsFemale )
	{
		KFPRI.TTSSpeaker = ETTSSpeaker(Rand(4));
	}
	else
	{
		KFPRI.TTSSpeaker = ETTSSpeaker(Rand(5) + 4);
	}
}

/** Sets the pawns character mesh from it's CharacterInfo, and updates instance of player in map if there is one. */
simulated function SetCharacterMeshFromArch( KFPawn KFP, optional KFPlayerReplicationInfo KFPRI )
{
	local int AttachmentIdx, CosmeticMeshIdx;
	local bool bMaskHeadMesh;
	//local int NumberOfCosmetics, NumberOfCosmeticsPostRemoval;

   	super.SetCharacterMeshFromArch( KFP, KFPRI );

   	if ( KFPRI == none )
	{
        `Warn("Does not have a KFPRI" @ Self);
	 	return;
	}

	// Body mesh & skin. Index of 255 implies use index 0 (default).
    SetBodyMeshAndSkin(
    	KFPRI.RepCustomizationInfo.BodyMeshIndex,
    	KFPRI.RepCustomizationInfo.BodySkinIndex,
    	KFP, KFPRI);

    // Head mesh & skin. Index of 255 implies use index 0 (default).
	SetHeadMeshAndSkin(
		KFPRI.RepCustomizationInfo.HeadMeshIndex,
		KFPRI.RepCustomizationInfo.HeadSkinIndex,
		KFP, KFPRI);

	// First person arms mesh and skin are based on body mesh & skin.
	// Index of 255 implies use index 0 (default).
	SetArmsMeshAndSkin(
    	KFPRI.RepCustomizationInfo.BodyMeshIndex,
    	KFPRI.RepCustomizationInfo.BodySkinIndex,
    	KFP, KFPRI);

	// skip dedicated for purely cosmetic stuff
	if ( KFP.WorldInfo.NetMode != NM_DedicatedServer )
	{
		// Must clear all attachments before trying to attach new ones,
		// otherwise we might accidentally remove things we're not supposed to
		for( AttachmentIdx=0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++ )
		{
			// Clear any previous attachments from other characters
			DetachAttachment(AttachmentIdx, KFP);
		}

		// Cosmetic attachment mesh & skin. Index of 255 implies don't use any attachments (default)
		for( AttachmentIdx=0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++ )
		{
			CosmeticMeshIdx = KFPRI.RepCustomizationInfo.AttachmentMeshIndices[AttachmentIdx];
			if ( CosmeticMeshIdx != `CLEARED_ATTACHMENT_INDEX && CosmeticMeshIdx != INDEX_NONE)
			{
					//NumberOfCosmetics++;
					bMaskHeadMesh = bMaskHeadMesh || CosmeticVariants[CosmeticMeshIdx].bMaskHeadMesh;
					
					// Attach all saved attachments to the character
					SetAttachmentMeshAndSkin(
						CosmeticMeshIdx,
						KFPRI.RepCustomizationInfo.AttachmentSkinIndices[AttachmentIdx],
						KFP, KFPRI);
			}
		}

		// Check again for all cosmetic in case one of them was removed, this will fix problems with masked heads
		/*for( AttachmentIdx=0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++ )
		{
			CosmeticMeshIdx = KFPRI.RepCustomizationInfo.AttachmentMeshIndices[AttachmentIdx];
			if ( CosmeticMeshIdx != `CLEARED_ATTACHMENT_INDEX && CosmeticMeshIdx != INDEX_NONE)
			{
					NumberOfCosmeticsPostRemoval++;
			}
		}*/

		// If the number of cosmetics changed, made a new set of the mesh/cosmetics recursively and return
		/*if(NumberOfCosmeticsPostRemoval != NumberOfCosmetics)
		{
			SetCharacterMeshFromArch( KFP, KFPRI );
			return;
		}*/

		InitCharacterMICs(KFP, bMaskHeadMesh);
	}
}

/** Create all the MICs we'll need for material params later */
private function InitCharacterMICs(KFPawn P, optional bool bMaskHead)
{
	local int i;
	
	if( P.WorldInfo.NetMode == NM_DedicatedServer )
	{
		return;
	}

	P.CharacterMICs.Remove(0, P.CharacterMICs.Length);

	// body MIC
	if ( P.Mesh != None )
	{
		P.CharacterMICs[0] = P.Mesh.CreateAndSetMaterialInstanceConstant(BodyMaterialID);
	}

	// head MIC
	if( P.ThirdPersonHeadMeshComponent != None )
	{
		P.CharacterMICs[1] = P.ThirdPersonHeadMeshComponent.CreateAndSetMaterialInstanceConstant(HeadMaterialID);

		if ( bMaskHead )
		{
			// initial mask for new head MIC (also see ResetHeadMaskParam())
			P.CharacterMICs[1].SetScalarParameterValue('Scalar_Mask', 1.f);
		}
	}

	// attachment MIC
	for( i=0; i < `MAX_COSMETIC_ATTACHMENTS; i++ )
	{
		if( P.ThirdPersonAttachments[i] != none )
		{
			P.CharacterMICs.AddItem(P.ThirdPersonAttachments[i].CreateAndSetMaterialInstanceConstant(0));
		}

		if (P.FirstPersonAttachments[i] != none)
		{
			P.CharacterMICs.AddItem(P.FirstPersonAttachments[i].CreateAndSetMaterialInstanceConstant(0));
		}
	}
}

private function SetBodyMeshAndSkin(
	byte CurrentBodyMeshIndex,
	byte CurrentBodySkinIndex,
	KFPawn KFP,
	KFPlayerReplicationInfo KFPRI)
{
	local string CharBodyMeshName;
	local SkeletalMesh CharBodyMesh;

    //Always use default body on servers
    if (KFP.WorldInfo.NetMode == NM_DedicatedServer)
    {
        CurrentBodyMeshIndex = 0;
        CurrentBodySkinIndex = 0;
    }

	// Character Mesh
	if( BodyVariants.length > 0 )
	{
		// Assign a skin to the body mesh as a material override
		CurrentBodyMeshIndex = (CurrentBodyMeshIndex < BodyVariants.length) ? CurrentBodyMeshIndex : 0;

		if (KFPRI.StartLoadCosmeticContent(self, ECOSMETICTYPE_Body, CurrentBodyMeshIndex))
		{
			return;
		}

		// Retrieve the name of the meshes to be used from the archetype
		CharBodyMeshName = BodyVariants[CurrentBodyMeshIndex].MeshName;

		// Load the meshes
		CharBodyMesh = SkeletalMesh(DynamicLoadObject(CharBodyMeshName, class'SkeletalMesh'));

		// Assign the body mesh to the pawn
		if ( CharBodyMesh != KFP.Mesh.SkeletalMesh )
		{
			KFP.Mesh.SetSkeletalMesh(CharBodyMesh);
			KFP.OnCharacterMeshChanged();
		}

		if (KFP.WorldInfo.NetMode != NM_DedicatedServer)
		{
			SetBodySkinMaterial(BodyVariants[CurrentBodyMeshIndex], CurrentBodySkinIndex, KFP);
		}
	}
	else
	{
		`warn("Character does not have a valid mesh");
	}
}

protected simulated function SetBodySkinMaterial(OutfitVariants CurrentVariant, byte NewSkinIndex, KFPawn KFP)
{
	local int i;
	if (KFP.WorldInfo.NetMode != NM_DedicatedServer)
	{
		if( CurrentVariant.SkinVariations.length > 0 )
		{
			// Assign a skin to the body mesh as a material override
			NewSkinIndex = (NewSkinIndex < CurrentVariant.SkinVariations.length) ? NewSkinIndex : 0;
			KFP.Mesh.SetMaterial(BodyMaterialID, CurrentVariant.SkinVariations[NewSkinIndex].Skin);
		}
		else
		{
			// Use material specified in the mesh asset
			for( i=0; i<KFP.Mesh.GetNumElements(); i++ )
			{
				KFP.Mesh.SetMaterial(i, none);
			}
		}
	}
}

protected simulated function SetHeadSkinMaterial(OutfitVariants CurrentVariant, byte NewSkinIndex, KFPawn KFP)
{
	local int i;
	if (KFP.WorldInfo.NetMode != NM_DedicatedServer)
	{
		if( CurrentVariant.SkinVariations.length > 0 )
		{
			// Assign a skin to the body mesh as a material override
			NewSkinIndex = (NewSkinIndex < CurrentVariant.SkinVariations.length) ? NewSkinIndex : 0;
			KFP.ThirdPersonHeadMeshComponent.SetMaterial(HeadMaterialID, CurrentVariant.SkinVariations[NewSkinIndex].Skin);
		}
		else
		{
			// Use material specified in the mesh asset
			for( i=0; i<KFP.ThirdPersonHeadMeshComponent.GetNumElements(); i++ )
			{
				KFP.ThirdPersonHeadMeshComponent.SetMaterial(i, none);
			}
		}
	}
}

private function SetHeadMeshAndSkin(
	byte CurrentHeadMeshIndex,
	byte CurrentHeadSkinIndex,
	KFPawn KFP,
	KFPlayerReplicationInfo KFPRI)
{
	local string CharHeadMeshName;
	local SkeletalMesh CharHeadMesh;

	if ( HeadVariants.length > 0 )
	{
		CurrentHeadMeshIndex = (CurrentHeadMeshIndex < HeadVariants.length) ? CurrentHeadMeshIndex : 0;

		if (KFPRI.StartLoadCosmeticContent(self, ECOSMETICTYPE_Head, CurrentHeadMeshIndex))
		{
			return;
		}

		CharHeadMeshName = HeadVariants[CurrentHeadMeshIndex].MeshName;
		CharHeadMesh = SkeletalMesh(DynamicLoadObject(CharHeadMeshName, class'SkeletalMesh'));

		// Parent the third person head mesh to the body mesh
		KFP.ThirdPersonHeadMeshComponent.SetSkeletalMesh(CharHeadMesh);
		KFP.ThirdPersonHeadMeshComponent.SetScale(DefaultMeshScale);

		KFP.ThirdPersonHeadMeshComponent.SetParentAnimComponent(KFP.Mesh);
		KFP.ThirdPersonHeadMeshComponent.SetShadowParent(KFP.Mesh);
		KFP.ThirdPersonHeadMeshComponent.SetLODParent(KFP.Mesh);

		KFP.AttachComponent(KFP.ThirdPersonHeadMeshComponent);

		if (KFP.WorldInfo.NetMode != NM_DedicatedServer)
		{
			SetHeadSkinMaterial(HeadVariants[CurrentHeadMeshIndex], CurrentHeadSkinIndex, KFP);
		}
	}
}

/**
 * Check to see if the attachment is supported for this mesh. Go through all the sockets of the mesh
 * and check if the socket for the attachment is present. If not, that attachment is not supported.
 * Network: Local Player Only
 */
function bool IsAttachmentAvailable(const out AttachmentVariants Attachment, Pawn PreviewPawn)
{
	if ( !class'KFUnlockManager'.static.GetAvailableAttachment(Attachment) )
	{
		//`log("Attachment" @ Attachment.MeshName @ "is not purchased.");
		return FALSE;
	}
	else if ( Attachment.AttachmentItem.SocketName != ''
		&& PreviewPawn.Mesh.GetSocketByName(Attachment.AttachmentItem.SocketName) == None )
	{
		`log("Attachment" @ Attachment.MeshName @ "is missing a required socket.");
		return FALSE;
	}

	return TRUE;
}

// Assigning the Arms Mesh default properties to the 1p cosmetic.
native function SetFirstPersonCosmeticAttachment(SkeletalMeshComponent SkeletalComponent);

protected simulated function SetAttachmentMesh(int CurrentAttachmentMeshIndex, int AttachmentSlotIndex, string CharAttachmentMeshName, name CharAttachmentSocketName, SkeletalMeshComponent PawnMesh, KFPawn KFP, bool bIsFirstPerson = false)
{
	local StaticMeshComponent StaticAttachment;
	local SkeletalMeshComponent SkeletalAttachment;
	local bool bIsSkeletalAttachment;
	local StaticMesh CharAttachmentStaticMesh;
	local SkeletalMesh CharacterAttachmentSkelMesh;
	local float MaxDrawDistance;
	local SkeletalMeshSocket AttachmentSocket;
	local vector AttachmentLocationRelativeToSocket, AttachmentScaleRelativeToSocket;
	local rotator AttachmentRotationRelativeToSocket;

	MaxDrawDistance = CosmeticVariants[CurrentAttachmentMeshIndex].AttachmentItem.MaxDrawDistance;
	AttachmentLocationRelativeToSocket = CosmeticVariants[CurrentAttachmentMeshIndex].RelativeTranslation;
	AttachmentRotationRelativeToSocket = CosmeticVariants[CurrentAttachmentMeshIndex].RelativeRotation;
	AttachmentScaleRelativeToSocket = CosmeticVariants[CurrentAttachmentMeshIndex].RelativeScale;
	bIsSkeletalAttachment = CosmeticVariants[CurrentAttachmentMeshIndex].AttachmentItem.bIsSkeletalAttachment;

	//`log("AttachmentLocationRelativeToSocket: x="$AttachmentLocationRelativeToSocket.x@"y="$AttachmentLocationRelativeToSocket.y@"z="$AttachmentLocationRelativeToSocket.z);
	// If it is a skeletal attachment, parent anim it to the body mesh
	if (bIsSkeletalAttachment)
	{
		if (bIsFirstPerson && (SkeletalMeshComponent(KFP.FirstPersonAttachments[AttachmentSlotIndex]) != none))
		{
			SkeletalAttachment = SkeletalMeshComponent(KFP.FirstPersonAttachments[AttachmentSlotIndex]);
		}
		else if (!bIsFirstPerson && (SkeletalMeshComponent(KFP.ThirdPersonAttachments[AttachmentSlotIndex]) != none))
		{
			SkeletalAttachment = SkeletalMeshComponent(KFP.ThirdPersonAttachments[AttachmentSlotIndex]);
		}
		else
		{
			SkeletalAttachment = new(KFP) class'KFSkeletalMeshComponent';
			if (bIsFirstPerson)
			{
				SetFirstPersonCosmeticAttachment(SkeletalAttachment);
			}
			SkeletalAttachment.SetActorCollision(false, false);

			if(bIsFirstPerson)
			{
				KFP.FirstPersonAttachments[AttachmentSlotIndex] = SkeletalAttachment;
			}
			else
			{
				KFP.ThirdPersonAttachments[AttachmentSlotIndex] = SkeletalAttachment;
			}
		}

		// Load and assign skeletal mesh
		CharacterAttachmentSkelMesh = SkeletalMesh(DynamicLoadObject(CharAttachmentMeshName, class'SkeletalMesh'));
		SkeletalAttachment.SetSkeletalMesh(CharacterAttachmentSkelMesh);

		// Parent animation and LOD transitions to body mesh
		SkeletalAttachment.SetParentAnimComponent(PawnMesh);
		SkeletalAttachment.SetLODParent(PawnMesh);
		SkeletalAttachment.SetScale(DefaultMeshScale);
		SkeletalAttachment.SetCullDistance(MaxDrawDistance);
		SkeletalAttachment.SetShadowParent(PawnMesh);
		SkeletalAttachment.SetLightingChannels(KFP.PawnLightingChannel);

		KFP.AttachComponent(SkeletalAttachment);
	}
	// Otherwise (if static), attach to a socket on the body mesh
	else
	{
		if (!bIsFirstPerson && (StaticMeshComponent(KFP.ThirdPersonAttachments[AttachmentSlotIndex]) != none))
		{
			StaticAttachment = StaticMeshComponent(KFP.ThirdPersonAttachments[AttachmentSlotIndex]);
		}
		else if (bIsFirstPerson && (StaticMeshComponent(KFP.FirstPersonAttachments[AttachmentSlotIndex]) != none))
		{
			StaticAttachment = StaticMeshComponent(KFP.FirstPersonAttachments[AttachmentSlotIndex]);
		}
		else
		{
			StaticAttachment = new(KFP) class'StaticMeshComponent';
			StaticAttachment.SetActorCollision(false, false);

			if(bIsFirstPerson)
			{
				KFP.FirstPersonAttachments[AttachmentSlotIndex] = StaticAttachment;
			}
			else
			{
				KFP.ThirdPersonAttachments[AttachmentSlotIndex] = StaticAttachment;
			}
		}

		// Load and assign static mesh
		CharAttachmentStaticMesh = StaticMesh(DynamicLoadObject(CharAttachmentMeshName, class'StaticMesh'));
		StaticAttachment.SetStaticMesh(CharAttachmentStaticMesh);

		// Set properties
		StaticAttachment.SetScale(DefaultMeshScale);
		StaticAttachment.SetCullDistance(MaxDrawDistance);
		StaticAttachment.SetShadowParent(KFP.Mesh);
		StaticAttachment.SetLightingChannels(KFP.PawnLightingChannel);

		// For static meshes, attach to given socket
		AttachmentSocket = PawnMesh.GetSocketByName(CharAttachmentSocketName);
		PawnMesh.AttachComponent(
			StaticAttachment,
			AttachmentSocket.BoneName,
			AttachmentSocket.RelativeLocation + AttachmentLocationRelativeToSocket,
			AttachmentSocket.RelativeRotation + AttachmentRotationRelativeToSocket,
			AttachmentSocket.RelativeScale * AttachmentScaleRelativeToSocket);
	}

	if(bIsFirstPerson)
	{
		KFP.FirstPersonAttachmentSocketNames[AttachmentSlotIndex] = CharAttachmentSocketName;
	}
	else
	{
		KFP.ThirdPersonAttachmentSocketNames[AttachmentSlotIndex] = CharAttachmentSocketName;
	}
}

protected simulated function SetAttachmentSkinMaterial(
	int PawnAttachmentIndex,
	const out AttachmentVariants CurrentVariant,
	byte NewSkinIndex,
	KFPawn KFP,
	optional bool bIsFirstPerson)
{
	local int i;
	if (KFP.WorldInfo.NetMode != NM_DedicatedServer)
	{
		if( CurrentVariant.AttachmentItem.SkinVariations.length > 0 )
		{
			// Assign a skin to the attachment mesh as a material override
			if ( NewSkinIndex < CurrentVariant.AttachmentItem.SkinVariations.length )
			{
				if (bIsFirstPerson)
				{
					if (KFP.FirstPersonAttachments[PawnAttachmentIndex] != none)
					{
						KFP.FirstPersonAttachments[PawnAttachmentIndex].SetMaterial(
							CurrentVariant.AttachmentItem.SkinMaterialID,
							CurrentVariant.AttachmentItem.SkinVariations[NewSkinIndex].Skin1p);
					}
				}
				else
				{
					KFP.ThirdPersonAttachments[PawnAttachmentIndex].SetMaterial(
						CurrentVariant.AttachmentItem.SkinMaterialID,
						CurrentVariant.AttachmentItem.SkinVariations[NewSkinIndex].Skin);
				}
			}
			else
			{
				`log("Out of bounds skin index for" @ CurrentVariant.MeshName);
				RemoveAttachmentMeshAndSkin(PawnAttachmentIndex, KFP);
			}
		}
		else
		{
			if (bIsFirstPerson)
			{
				if (KFP.FirstPersonAttachments[PawnAttachmentIndex] != none)
				{
					for (i = 0; i < KFP.FirstPersonAttachments[PawnAttachmentIndex].GetNumElements(); i++)
					{
						KFP.FirstPersonAttachments[PawnAttachmentIndex].SetMaterial(i, none);
					}
				}
			}
			else
			{
				// Use material specified in the mesh asset
				for( i=0; i < KFP.ThirdPersonAttachments[PawnAttachmentIndex].GetNumElements(); i++ )
				{
					KFP.ThirdPersonAttachments[PawnAttachmentIndex].SetMaterial(i, none);
				}
			}
		}
	}
}

/** Called on owning client to change a cosmetic attachment or on other clients via replication */
private function SetAttachmentMeshAndSkin(
	int CurrentAttachmentMeshIndex,
	int CurrentAttachmentSkinIndex,
	KFPawn KFP,
	KFPlayerReplicationInfo KFPRI)
{
	local string CharAttachmentMeshName;
	local name CharAttachmentSocketName;
	local int AttachmentSlotIndex;
	if (KFP.WorldInfo.NetMode == NM_DedicatedServer)
	{
		return;
	}

	// Clear any previously attachments for the same slot
	//DetachConflictingAttachments(CurrentAttachmentMeshIndex, KFP, KFPRI);
	// Get a slot where this attachment could fit
	AttachmentSlotIndex = GetAttachmentSlotIndex(CurrentAttachmentMeshIndex, KFP, KFPRI);
	if (AttachmentSlotIndex == INDEX_NONE)
	{
		return;
	}

	// Since cosmetic attachments are optional, do not choose index 0 if none is
	// specified unlike the the head and body meshes
	if ( CosmeticVariants.length > 0 &&
		 CurrentAttachmentMeshIndex < CosmeticVariants.length )
	{
		if (KFPRI.StartLoadCosmeticContent(self, ECOSMETICTYPE_Attachment, CurrentAttachmentMeshIndex))
		{
			return;
		}

		// Attach 1p attachment, if applicable
		CharAttachmentMeshName = Get1pMeshByIndex(CurrentAttachmentMeshIndex);
		if (CharAttachmentMeshName != "")
		{
			CharAttachmentSocketName = CosmeticVariants[CurrentAttachmentMeshIndex].AttachmentItem.SocketName1p;

			// If previously attached and we could have changed outfits (e.g. local player UI) then re-validate
			// required skeletal mesh socket.  Must be after body mesh DLO, but before AttachComponent.
			if ( KFP.IsLocallyControlled() )
			{
				if ( CharAttachmentSocketName != '' && KFP.Mesh.GetSocketByName(CharAttachmentSocketName) == None )
				{
					RemoveAttachmentMeshAndSkin(AttachmentSlotIndex, KFP, KFPRI);
					return;
				}
			}

			SetAttachmentMesh(CurrentAttachmentMeshIndex, AttachmentSlotIndex, CharAttachmentMeshName,
				CharAttachmentSocketName, KFP.ArmsMesh, KFP, true);

			SetAttachmentSkinMaterial(
				AttachmentSlotIndex,
				CosmeticVariants[CurrentAttachmentMeshIndex],
				CurrentAttachmentSkinIndex,
				KFP,
				true);
		}
		else
		{
			KFP.FirstPersonAttachments[AttachmentSlotIndex] = none;
			KFP.FirstPersonAttachmentSocketNames[AttachmentSlotIndex] = '';
		}

		// Attach 3p attachment, if applicable
		CharAttachmentMeshName = GetMeshByIndex(CurrentAttachmentMeshIndex);
		if (CharAttachmentMeshName != "")
		{
			CharAttachmentSocketName = CosmeticVariants[CurrentAttachmentMeshIndex].AttachmentItem.SocketName;

			// If previously attached and we could have changed outfits (e.g. local player UI) then re-validate
			// required skeletal mesh socket.  Must be after body mesh DLO, but before AttachComponent.
			if ( KFP.IsLocallyControlled() )
			{
				if ( CharAttachmentSocketName != '' && KFP.Mesh.GetSocketByName(CharAttachmentSocketName) == None )
				{
					RemoveAttachmentMeshAndSkin(AttachmentSlotIndex, KFP, KFPRI);
					return;
				}
			}

			SetAttachmentMesh(CurrentAttachmentMeshIndex, AttachmentSlotIndex, CharAttachmentMeshName,
				CharAttachmentSocketName, KFP.Mesh, KFP, false);

			SetAttachmentSkinMaterial(
				AttachmentSlotIndex,
				CosmeticVariants[CurrentAttachmentMeshIndex],
				CurrentAttachmentSkinIndex,
				KFP,
				false);
		}
	}

	// Treat `CLEARED_ATTACHMENT_INDEX as special value (for client detachment)
	if( CurrentAttachmentMeshIndex == `CLEARED_ATTACHMENT_INDEX )
	{
		RemoveAttachmentMeshAndSkin(AttachmentSlotIndex, KFP, KFPRI);
	}
}

/**
 * Removes any attachments that exist in the same socket or have overriding cases
 * Network: Local Player
 */
function DetachConflictingAttachments(int NewAttachmentMeshIndex, KFPawn KFP, optional KFPlayerReplicationInfo KFPRI, optional out array<int> out_RemovedAttachments )
{
	local name NewAttachmentSocketName;
	local int i, CurrentAttachmentIdx;

	if ( CosmeticVariants.length > 0 &&
		 NewAttachmentMeshIndex < CosmeticVariants.length && NewAttachmentMeshIndex != INDEX_NONE)
	{
		// The socket that this attachment requires
		NewAttachmentSocketName = CosmeticVariants[NewAttachmentMeshIndex].AttachmentItem.SocketName;

		for( i=0; i < `MAX_COSMETIC_ATTACHMENTS; i++ )
		{
			CurrentAttachmentIdx = KFPRI.RepCustomizationInfo.AttachmentMeshIndices[i];
			if ( CurrentAttachmentIdx == `CLEARED_ATTACHMENT_INDEX )
				continue;

			// Remove the object if it is taking up our desired slot
			if( KFP.ThirdPersonAttachmentSocketNames[i] != '' &&
				KFP.ThirdPersonAttachmentSocketNames[i] == NewAttachmentSocketName )
			{
				RemoveAttachmentMeshAndSkin(i, KFP, KFPRI);
				out_RemovedAttachments.AddItem(i);
				continue;
			}

			// Remove the object if it cannot exist at the same time as another equipped item
			if( GetOverrideCase(CurrentAttachmentIdx, NewAttachmentMeshIndex) )
			{
				RemoveAttachmentMeshAndSkin(i, KFP, KFPRI);
				out_RemovedAttachments.AddItem(i);
				continue;
			}

			// Check inverse override
			if( GetOverrideCase(NewAttachmentMeshIndex, CurrentAttachmentIdx) )
			{
				RemoveAttachmentMeshAndSkin(i, KFP, KFPRI);
				out_RemovedAttachments.AddItem(i);
				continue;
			}
		}
	}
}

/**
 * Removes any attachments that exist in the same socket or have overriding cases
 * Network: Local Player
 */
function bool GetOverrideCase(int AttachmentIndex1, int AttachmentIndex2)
{
	local KFCharacterAttachment Attachment1;

	if (AttachmentIndex1 == INDEX_NONE || AttachmentIndex2 == INDEX_NONE)
	{
		return false;
	}

	//dont try to access out of bound elements
	if (AttachmentIndex1 >= CosmeticVariants.Length || AttachmentIndex2 >= CosmeticVariants.Length)
		return FALSE;

	Attachment1 = CosmeticVariants[AttachmentIndex1].AttachmentItem;

	if (CosmeticVariants[AttachmentIndex2].SpecialOverrideAttachments.length > 0)
	{
		if (CosmeticVariants[AttachmentIndex2].SpecialOverrideAttachments.Find(Attachment1) != INDEX_NONE)
		{
			return TRUE;
		}
	}
	else
	{
		if (CosmeticVariants[AttachmentIndex2].AttachmentItem.DefaultSpecialOverrideAttachments.Find(Attachment1) != INDEX_NONE)
		{
			return TRUE;
		}
	}
	switch (CosmeticVariants[AttachmentIndex1].AttachmentItem.SocketName)
	{
	case 'Hat_Attach':
		return CosmeticVariants[AttachmentIndex2].AttachmentItem.OverrideList.bHat;
	case 'Face_Attach':
		return CosmeticVariants[AttachmentIndex2].AttachmentItem.OverrideList.bFace;
	case 'Eyes_Attach':
		return CosmeticVariants[AttachmentIndex2].AttachmentItem.OverrideList.bEyes;
	case 'Jaw_Attach':
		return CosmeticVariants[AttachmentIndex2].AttachmentItem.OverrideList.bJaw;
	case 'Armband_Attach':
		return CosmeticVariants[AttachmentIndex2].AttachmentItem.OverrideList.bArmband;
	case 'Backpack_Attach':
		return CosmeticVariants[AttachmentIndex2].AttachmentItem.OverrideList.bBackpack;
	}

	return false;
}

/** Called on owning client to get the most appropriate slot for a cosmetic attachment.
	@returns -1 if no available slot otherwise returns the next available. If there is
	a socket conflict with an existing attachment, it returns the index of the existing
	attachment (to be replaced)
 */
function int GetAttachmentSlotIndex(
	int CurrentAttachmentMeshIndex,
	KFPawn KFP,
	KFPlayerReplicationInfo KFPRI)
{
	local int AttachmentIdx;

	if (KFPRI == none)
	{
		`warn("GetAttachmentSlotIndex - NO KFPRI");
		return INDEX_NONE;
	}

	// Return the next available attachment index or the index that matches this mesh
	for( AttachmentIdx=0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++ )
	{
		if (KFPRI.RepCustomizationInfo.AttachmentMeshIndices[AttachmentIdx] == CurrentAttachmentMeshIndex)
		{
			return AttachmentIdx;
		}
	}
	return INDEX_NONE;
}

/** Called on owning client directly to remove a cosmetic attachment, or by
	SetAttachmentMeshAndSkin() on a replicated client when attachment index is `CLEARED_ATTACHMENT_INDEX
 */
simulated function RemoveAttachmentMeshAndSkin(
	int PawnAttachmentIndex,
	KFPawn KFP,
	optional KFPlayerReplicationInfo KFPRI)
{
	// Detach the attachment
	DetachAttachment(PawnAttachmentIndex, KFP);

	// Notify clients
	if( KFPRI != none )
	{
		KFPRI.ClearCharacterAttachment(PawnAttachmentIndex);
    }
}

/** Remove the attachment from the mesh */
function DetachAttachment(int PawnAttachmentIndex, KFPawn KFP)
{
	// Detach the attachment
	if( KFP.ThirdPersonAttachments[PawnAttachmentIndex] != none )
	{
        // We have a different attachment path if it is a skeletal mesh component
		if( SkeletalMeshComponent(KFP.ThirdPersonAttachments[PawnAttachmentIndex]) != none )
		{
			KFP.DetachComponent(KFP.ThirdPersonAttachments[PawnAttachmentIndex]);
			if (KFP.FirstPersonAttachments[PawnAttachmentIndex] != none)
			{
				KFP.DetachComponent(KFP.FirstPersonAttachments[PawnAttachmentIndex]);
			}
		}
		else
		{
			KFP.mesh.DetachComponent(KFP.ThirdPersonAttachments[PawnAttachmentIndex]);
			if (KFP.FirstPersonAttachments[PawnAttachmentIndex] != none)
			{
				KFP.ArmsMesh.DetachComponent(KFP.FirstPersonAttachments[PawnAttachmentIndex]);
			}
		}
	}

	// Update the pawn's attachment metadata
	KFP.ThirdPersonAttachmentSocketNames[PawnAttachmentIndex] = '';
	KFP.FirstPersonAttachmentSocketNames[PawnAttachmentIndex] = '';
}

/** Assign an arm mesh and material to this pawn */
simulated function SetFirstPersonArmsFromArch( KFPawn KFP, optional KFPlayerReplicationInfo KFPRI )
{
	local int CosmeticMeshIdx, AttachmentIdx;

	if ( KFPRI == none )
	{
        `Warn("Does not have a KFPRI" @ Self);
	 	return;
	}

	// First person arms mesh and skin are based on body mesh & skin.
	// Index of 255 implies use index 0 (default).
    SetArmsMeshAndSkin(
    	KFPRI.RepCustomizationInfo.BodyMeshIndex,
    	KFPRI.RepCustomizationInfo.BodySkinIndex,
    	KFP,
		KFPRI);

	// Cosmetic attachment first person mesh & skin. Index of 255 implies don't use any attachments (default)
	// Don't have to worry about clearing out attachments since that was handled in 3rd person cosmetics.
	for (AttachmentIdx = 0; AttachmentIdx < `MAX_COSMETIC_ATTACHMENTS; AttachmentIdx++)
	{
		CosmeticMeshIdx = KFPRI.RepCustomizationInfo.AttachmentMeshIndices[AttachmentIdx];
		if (CosmeticMeshIdx != `CLEARED_ATTACHMENT_INDEX && CosmeticMeshIdx != INDEX_NONE)
		{
			`log("Attach 1p mesh" @ CosmeticMeshIdx);

			// Attach all saved attachments to the character
			SetAttachmentMeshAndSkin(
				CosmeticMeshIdx,
				KFPRI.RepCustomizationInfo.AttachmentSkinIndices[AttachmentIdx],
				KFP, KFPRI);
		}
	}
}

simulated function SetArmsMeshAndSkin(
	byte ArmsMeshIndex,
	byte ArmsSkinIndex,
	KFPawn KFP,
	KFPlayerReplicationInfo KFPRI)
{
	local byte CurrentArmMeshIndex, CurrentArmSkinIndex;
	local string CharArmMeshName;
	local SkeletalMesh CharArmMesh;

	if (KFP.WorldInfo.NetMode != NM_DedicatedServer && KFP.IsHumanControlled() && KFP.IsLocallyControlled())
	{
		// Character mesh
		if( CharacterArmVariants.length > 0 )
		{
			CurrentArmMeshIndex = (ArmsMeshIndex < CharacterArmVariants.length) ? ArmsMeshIndex : 0;
			if (KFPRI.StartLoadCosmeticContent(self, ECOSMETICTYPE_Arms, CurrentArmMeshIndex))
			{
				return;
			}

			// Retrieve the name of the meshes to be used from the archetype
			CharArmMeshName = CharacterArmVariants[CurrentArmMeshIndex].MeshName;

			// Load the meshes
			CharArmMesh = SkeletalMesh(DynamicLoadObject(CharArmMeshName, class'SkeletalMesh'));

			// Assign the arms mesh to the pawn
			KFP.ArmsMesh.SetSkeletalMesh(CharArmMesh);

			if( CharacterArmVariants[CurrentArmMeshIndex].SkinVariants.length > 0 )
			{
				// Assign a skin to the arms mesh as a material override
				CurrentArmSkinIndex = (ArmsSkinIndex < CharacterArmVariants[CurrentArmMeshIndex].SkinVariants.length) ? ArmsSkinIndex : 0;
				KFP.ArmsMesh.SetMaterial(0, CharacterArmVariants[CurrentArmMeshIndex].SkinVariants[CurrentArmSkinIndex]);
			}
			else
			{
				// Use material specified in the asset
				KFP.ArmsMesh.SetMaterial(0, none);
			}
		}
		else if( ArmMesh != none )
		{
			// Clear character customization settings
			KFP.ArmsMesh.SetMaterial(0, none);

	        // Assign the arms mesh to the pawn
	        KFP.ArmsMesh.SetSkeletalMesh(ArmMesh);
		}
		else
		{
			`warn("Character does not have a valid arms mesh");
		}
	}
}

static function int GetWeaponAnimSetIdx()
{
	return default.EmoteAnimset != none ? default.AnimSets.length + 1 : default.AnimSets.length;
}

simulated function SetCharacterAnimFromArch( KFPawn Pawn )
{
	super.SetCharacterAnimFromArch( Pawn );

	if( EmoteAnimset != none )
	{
		Pawn.Mesh.Animsets.AddItem(EmoteAnimset);
	}
}

defaultproperties
{
	SoundGroupArch=KFPawnSoundGroup'FX_Pawn_Sounds_ARCH.HumanPawnSounds'
	EmoteAnimset=AnimSet'ECON_emote.ECON_Emotes'
}