1
0
KF2-Dev-Scripts/KFGame/Classes/KFCharacterInfo_Human.uc
2020-12-13 18:01:13 +03:00

1168 lines
38 KiB
Ucode

//=============================================================================
// 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)
{
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);
continue;
}
// Remove the object if it cannot exist at the same time as another equipped item
if( GetOverrideCase(CurrentAttachmentIdx, NewAttachmentMeshIndex) )
{
RemoveAttachmentMeshAndSkin(i, KFP, KFPRI);
continue;
}
// Check inverse override
if( GetOverrideCase(NewAttachmentMeshIndex, CurrentAttachmentIdx) )
{
RemoveAttachmentMeshAndSkin(i, KFP, KFPRI);
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] == INDEX_NONE ||
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'
}