340 lines
9.3 KiB
C++
340 lines
9.3 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LyraPawnComponent_CharacterParts.h"
|
|
#include "GameFramework/Character.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Components/ChildActorComponent.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
#include "GameplayTagAssetInterface.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
FString FLyraAppliedCharacterPartEntry::GetDebugString() const
|
|
{
|
|
return FString::Printf(TEXT("(PartClass: %s, Socket: %s, Instance: %s)"), *GetPathNameSafe(Part.PartClass), *Part.SocketName.ToString(), *GetPathNameSafe(SpawnedComponent));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void FLyraCharacterPartList::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
|
{
|
|
bool bDestroyedAnyActors = false;
|
|
for (int32 Index : RemovedIndices)
|
|
{
|
|
FLyraAppliedCharacterPartEntry& Entry = Entries[Index];
|
|
bDestroyedAnyActors |= DestroyActorForEntry(Entry);
|
|
}
|
|
|
|
if (bDestroyedAnyActors)
|
|
{
|
|
OwnerComponent->BroadcastChanged();
|
|
}
|
|
}
|
|
|
|
void FLyraCharacterPartList::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
|
{
|
|
bool bCreatedAnyActors = false;
|
|
for (int32 Index : AddedIndices)
|
|
{
|
|
FLyraAppliedCharacterPartEntry& Entry = Entries[Index];
|
|
bCreatedAnyActors |= SpawnActorForEntry(Entry);
|
|
}
|
|
|
|
if (bCreatedAnyActors)
|
|
{
|
|
OwnerComponent->BroadcastChanged();
|
|
}
|
|
}
|
|
|
|
void FLyraCharacterPartList::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
|
{
|
|
bool bChangedAnyActors = false;
|
|
|
|
// We don't support dealing with propagating changes, just destroy and recreate
|
|
for (int32 Index : ChangedIndices)
|
|
{
|
|
FLyraAppliedCharacterPartEntry& Entry = Entries[Index];
|
|
|
|
bChangedAnyActors |= DestroyActorForEntry(Entry);
|
|
bChangedAnyActors |= SpawnActorForEntry(Entry);
|
|
}
|
|
|
|
if (bChangedAnyActors)
|
|
{
|
|
OwnerComponent->BroadcastChanged();
|
|
}
|
|
}
|
|
|
|
FLyraCharacterPartHandle FLyraCharacterPartList::AddEntry(FLyraCharacterPart NewPart)
|
|
{
|
|
FLyraCharacterPartHandle Result;
|
|
Result.PartHandle = PartHandleCounter++;
|
|
|
|
if (ensure(OwnerComponent && OwnerComponent->GetOwner() && OwnerComponent->GetOwner()->HasAuthority()))
|
|
{
|
|
FLyraAppliedCharacterPartEntry& NewEntry = Entries.AddDefaulted_GetRef();
|
|
NewEntry.Part = NewPart;
|
|
NewEntry.PartHandle = Result.PartHandle;
|
|
|
|
if (SpawnActorForEntry(NewEntry))
|
|
{
|
|
OwnerComponent->BroadcastChanged();
|
|
}
|
|
|
|
MarkItemDirty(NewEntry);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void FLyraCharacterPartList::RemoveEntry(FLyraCharacterPartHandle Handle)
|
|
{
|
|
for (auto EntryIt = Entries.CreateIterator(); EntryIt; ++EntryIt)
|
|
{
|
|
FLyraAppliedCharacterPartEntry& Entry = *EntryIt;
|
|
if (Entry.PartHandle == Handle.PartHandle)
|
|
{
|
|
const bool bDestroyedActor = DestroyActorForEntry(Entry);
|
|
EntryIt.RemoveCurrent();
|
|
MarkArrayDirty();
|
|
|
|
if (bDestroyedActor)
|
|
{
|
|
OwnerComponent->BroadcastChanged();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLyraCharacterPartList::ClearAllEntries(bool bBroadcastChangeDelegate)
|
|
{
|
|
bool bDestroyedAnyActors = false;
|
|
for (FLyraAppliedCharacterPartEntry& Entry : Entries)
|
|
{
|
|
bDestroyedAnyActors |= DestroyActorForEntry(Entry);
|
|
}
|
|
Entries.Reset();
|
|
MarkArrayDirty();
|
|
|
|
if (bDestroyedAnyActors && bBroadcastChangeDelegate)
|
|
{
|
|
OwnerComponent->BroadcastChanged();
|
|
}
|
|
}
|
|
|
|
FGameplayTagContainer FLyraCharacterPartList::CollectCombinedTags() const
|
|
{
|
|
FGameplayTagContainer Result;
|
|
|
|
for (const FLyraAppliedCharacterPartEntry& Entry : Entries)
|
|
{
|
|
if (Entry.SpawnedComponent != nullptr)
|
|
{
|
|
if (IGameplayTagAssetInterface* TagInterface = Cast<IGameplayTagAssetInterface>(Entry.SpawnedComponent->GetChildActor()))
|
|
{
|
|
TagInterface->GetOwnedGameplayTags(/*inout*/ Result);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool FLyraCharacterPartList::SpawnActorForEntry(FLyraAppliedCharacterPartEntry& Entry)
|
|
{
|
|
bool bCreatedAnyActors = false;
|
|
|
|
if (!OwnerComponent->IsNetMode(NM_DedicatedServer))
|
|
{
|
|
if (Entry.Part.PartClass != nullptr)
|
|
{
|
|
UWorld* World = OwnerComponent->GetWorld();
|
|
|
|
if (USceneComponent* ComponentToAttachTo = OwnerComponent->GetSceneComponentToAttachTo())
|
|
{
|
|
const FTransform SpawnTransform = ComponentToAttachTo->GetSocketTransform(Entry.Part.SocketName);
|
|
|
|
UChildActorComponent* PartComponent = NewObject<UChildActorComponent>(OwnerComponent->GetOwner());
|
|
|
|
PartComponent->SetupAttachment(ComponentToAttachTo, Entry.Part.SocketName);
|
|
PartComponent->SetChildActorClass(Entry.Part.PartClass);
|
|
PartComponent->RegisterComponent();
|
|
|
|
if (AActor* SpawnedActor = PartComponent->GetChildActor())
|
|
{
|
|
switch (Entry.Part.CollisionMode)
|
|
{
|
|
case ECharacterCustomizationCollisionMode::UseCollisionFromCharacterPart:
|
|
// Do nothing
|
|
break;
|
|
|
|
case ECharacterCustomizationCollisionMode::NoCollision:
|
|
SpawnedActor->SetActorEnableCollision(false);
|
|
break;
|
|
}
|
|
|
|
// Set up a direct tick dependency to work around the child actor component not providing one
|
|
if (USceneComponent* SpawnedRootComponent = SpawnedActor->GetRootComponent())
|
|
{
|
|
SpawnedRootComponent->AddTickPrerequisiteComponent(ComponentToAttachTo);
|
|
}
|
|
}
|
|
|
|
Entry.SpawnedComponent = PartComponent;
|
|
bCreatedAnyActors = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bCreatedAnyActors;
|
|
}
|
|
|
|
bool FLyraCharacterPartList::DestroyActorForEntry(FLyraAppliedCharacterPartEntry& Entry)
|
|
{
|
|
bool bDestroyedAnyActors = false;
|
|
|
|
if (Entry.SpawnedComponent != nullptr)
|
|
{
|
|
Entry.SpawnedComponent->DestroyComponent();
|
|
Entry.SpawnedComponent = nullptr;
|
|
bDestroyedAnyActors = true;
|
|
}
|
|
|
|
return bDestroyedAnyActors;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
ULyraPawnComponent_CharacterParts::ULyraPawnComponent_CharacterParts(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, CharacterPartList(this)
|
|
{
|
|
SetIsReplicatedByDefault(true);
|
|
}
|
|
|
|
void ULyraPawnComponent_CharacterParts::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
DOREPLIFETIME(ThisClass, CharacterPartList);
|
|
}
|
|
|
|
FLyraCharacterPartHandle ULyraPawnComponent_CharacterParts::AddCharacterPart(const FLyraCharacterPart& NewPart)
|
|
{
|
|
return CharacterPartList.AddEntry(NewPart);
|
|
}
|
|
|
|
void ULyraPawnComponent_CharacterParts::RemoveCharacterPart(FLyraCharacterPartHandle Handle)
|
|
{
|
|
CharacterPartList.RemoveEntry(Handle);
|
|
}
|
|
|
|
void ULyraPawnComponent_CharacterParts::RemoveAllCharacterParts()
|
|
{
|
|
CharacterPartList.ClearAllEntries(/*bBroadcastChangeDelegate=*/ true);
|
|
}
|
|
|
|
void ULyraPawnComponent_CharacterParts::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
}
|
|
|
|
void ULyraPawnComponent_CharacterParts::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
{
|
|
CharacterPartList.ClearAllEntries(/*bBroadcastChangeDelegate=*/ false);
|
|
|
|
Super::EndPlay(EndPlayReason);
|
|
}
|
|
|
|
TArray<AActor*> ULyraPawnComponent_CharacterParts::GetCharacterPartActors() const
|
|
{
|
|
TArray<AActor*> Result;
|
|
Result.Reserve(CharacterPartList.Entries.Num());
|
|
|
|
for (const FLyraAppliedCharacterPartEntry& Entry : CharacterPartList.Entries)
|
|
{
|
|
if (UChildActorComponent* PartComponent = Entry.SpawnedComponent)
|
|
{
|
|
if (AActor* SpawnedActor = PartComponent->GetChildActor())
|
|
{
|
|
Result.Add(SpawnedActor);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
USkeletalMeshComponent* ULyraPawnComponent_CharacterParts::GetParentMeshComponent() const
|
|
{
|
|
if (AActor* OwnerActor = GetOwner())
|
|
{
|
|
if (ACharacter* OwningCharacter = Cast<ACharacter>(OwnerActor))
|
|
{
|
|
if (USkeletalMeshComponent* MeshComponent = OwningCharacter->GetMesh())
|
|
{
|
|
return MeshComponent;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
USceneComponent* ULyraPawnComponent_CharacterParts::GetSceneComponentToAttachTo() const
|
|
{
|
|
if (USkeletalMeshComponent* MeshComponent = GetParentMeshComponent())
|
|
{
|
|
return MeshComponent;
|
|
}
|
|
else if (AActor* OwnerActor = GetOwner())
|
|
{
|
|
return OwnerActor->GetRootComponent();
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
FGameplayTagContainer ULyraPawnComponent_CharacterParts::GetCombinedTags(FGameplayTag RequiredPrefix) const
|
|
{
|
|
FGameplayTagContainer Result = CharacterPartList.CollectCombinedTags();
|
|
if (RequiredPrefix.IsValid())
|
|
{
|
|
return Result.Filter(FGameplayTagContainer(RequiredPrefix));
|
|
}
|
|
else
|
|
{
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
void ULyraPawnComponent_CharacterParts::BroadcastChanged()
|
|
{
|
|
const bool bReinitPose = true;
|
|
|
|
// Check to see if the body type has changed
|
|
if (USkeletalMeshComponent* MeshComponent = GetParentMeshComponent())
|
|
{
|
|
// Determine the mesh to use based on cosmetic part tags
|
|
const FGameplayTagContainer MergedTags = GetCombinedTags(FGameplayTag());
|
|
USkeletalMesh* DesiredMesh = BodyMeshes.SelectBestBodyStyle(MergedTags);
|
|
|
|
// Apply the desired mesh (this call is a no-op if the mesh hasn't changed)
|
|
MeshComponent->SetSkeletalMesh(DesiredMesh, /*bReinitPose=*/ bReinitPose);
|
|
|
|
// Apply the desired physics asset if there's a forced override independent of the one from the mesh
|
|
if (UPhysicsAsset* PhysicsAsset = BodyMeshes.ForcedPhysicsAsset)
|
|
{
|
|
MeshComponent->SetPhysicsAsset(PhysicsAsset, /*bForceReInit=*/ bReinitPose);
|
|
}
|
|
}
|
|
|
|
// Let observers know, e.g., if they need to apply team coloring or similar
|
|
OnCharacterPartsChanged.Broadcast(this);
|
|
}
|
|
|