296 lines
11 KiB
C++
296 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "GameFeatureAction_AddAbilities.h"
|
|
#include "GameFeaturesSubsystem.h"
|
|
#include "Components/GameFrameworkComponentManager.h"
|
|
#include "GameFeaturesSubsystemSettings.h"
|
|
#include "Engine/AssetManager.h"
|
|
#include "AbilitySystemComponent.h"
|
|
#include "AttributeSet.h"
|
|
#include "AbilitySystem/LyraAbilitySystemComponent.h"
|
|
#include "Player/LyraPlayerState.h" //@TODO: For the fname
|
|
|
|
#define LOCTEXT_NAMESPACE "GameFeatures"
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// UGameFeatureAction_AddAbilities
|
|
|
|
void UGameFeatureAction_AddAbilities::OnGameFeatureActivating(FGameFeatureActivatingContext& Context)
|
|
{
|
|
FPerContextData& ActiveData = ContextData.FindOrAdd(Context);
|
|
|
|
if (!ensureAlways(ActiveData.ActiveExtensions.IsEmpty()) ||
|
|
!ensureAlways(ActiveData.ComponentRequests.IsEmpty()))
|
|
{
|
|
Reset(ActiveData);
|
|
}
|
|
Super::OnGameFeatureActivating(Context);
|
|
}
|
|
|
|
void UGameFeatureAction_AddAbilities::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context)
|
|
{
|
|
Super::OnGameFeatureDeactivating(Context);
|
|
FPerContextData* ActiveData = ContextData.Find(Context);
|
|
|
|
if (ensure(ActiveData))
|
|
{
|
|
Reset(*ActiveData);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
EDataValidationResult UGameFeatureAction_AddAbilities::IsDataValid(TArray<FText>& ValidationErrors)
|
|
{
|
|
EDataValidationResult Result = CombineDataValidationResults(Super::IsDataValid(ValidationErrors), EDataValidationResult::Valid);
|
|
|
|
int32 EntryIndex = 0;
|
|
for (const FGameFeatureAbilitiesEntry& Entry : AbilitiesList)
|
|
{
|
|
if (Entry.ActorClass.IsNull())
|
|
{
|
|
Result = EDataValidationResult::Invalid;
|
|
ValidationErrors.Add(FText::Format(LOCTEXT("EntryHasNullActor", "Null ActorClass at index {0} in AbilitiesList"), FText::AsNumber(EntryIndex)));
|
|
}
|
|
|
|
if (Entry.GrantedAbilities.IsEmpty() && Entry.GrantedAttributes.IsEmpty() && Entry.GrantedAbilitySets.IsEmpty())
|
|
{
|
|
Result = EDataValidationResult::Invalid;
|
|
ValidationErrors.Add(FText::Format(LOCTEXT("EntryHasNoAddOns", "Index {0} in AbilitiesList will do nothing (no granted abilities, attributes, or ability sets)"), FText::AsNumber(EntryIndex)));
|
|
}
|
|
|
|
int32 AbilityIndex = 0;
|
|
for (const FLyraAbilityGrant& Ability : Entry.GrantedAbilities)
|
|
{
|
|
if (Ability.AbilityType.IsNull())
|
|
{
|
|
Result = EDataValidationResult::Invalid;
|
|
ValidationErrors.Add(FText::Format(LOCTEXT("EntryHasNullAbility", "Null AbilityType at index {0} in AbilitiesList[{1}].GrantedAbilities"), FText::AsNumber(AbilityIndex), FText::AsNumber(EntryIndex)));
|
|
}
|
|
++AbilityIndex;
|
|
}
|
|
|
|
int32 AttributesIndex = 0;
|
|
for (const FLyraAttributeSetGrant& Attributes : Entry.GrantedAttributes)
|
|
{
|
|
if (Attributes.AttributeSetType.IsNull())
|
|
{
|
|
Result = EDataValidationResult::Invalid;
|
|
ValidationErrors.Add(FText::Format(LOCTEXT("EntryHasNullAttributeSet", "Null AttributeSetType at index {0} in AbilitiesList[{1}].GrantedAttributes"), FText::AsNumber(AttributesIndex), FText::AsNumber(EntryIndex)));
|
|
}
|
|
++AttributesIndex;
|
|
}
|
|
|
|
int32 AttributeSetIndex = 0;
|
|
for (const TSoftObjectPtr<const ULyraAbilitySet>& AttributeSetPtr : Entry.GrantedAbilitySets)
|
|
{
|
|
if (AttributeSetPtr.IsNull())
|
|
{
|
|
Result = EDataValidationResult::Invalid;
|
|
ValidationErrors.Add(FText::Format(LOCTEXT("EntryHasNullAttributeSet", "Null AbilitySet at index {0} in AbilitiesList[{1}].GrantedAbilitySets"), FText::AsNumber(AttributeSetIndex), FText::AsNumber(EntryIndex)));
|
|
}
|
|
++AttributeSetIndex;
|
|
}
|
|
++EntryIndex;
|
|
}
|
|
|
|
return Result;
|
|
|
|
return EDataValidationResult::NotValidated;
|
|
}
|
|
#endif
|
|
|
|
void UGameFeatureAction_AddAbilities::AddToWorld(const FWorldContext& WorldContext, const FGameFeatureStateChangeContext& ChangeContext)
|
|
{
|
|
UWorld* World = WorldContext.World();
|
|
UGameInstance* GameInstance = WorldContext.OwningGameInstance;
|
|
FPerContextData& ActiveData = ContextData.FindOrAdd(ChangeContext);
|
|
|
|
if ((GameInstance != nullptr) && (World != nullptr) && World->IsGameWorld())
|
|
{
|
|
if (UGameFrameworkComponentManager* ComponentMan = UGameInstance::GetSubsystem<UGameFrameworkComponentManager>(GameInstance))
|
|
{
|
|
int32 EntryIndex = 0;
|
|
for (const FGameFeatureAbilitiesEntry& Entry : AbilitiesList)
|
|
{
|
|
if (!Entry.ActorClass.IsNull())
|
|
{
|
|
UGameFrameworkComponentManager::FExtensionHandlerDelegate AddAbilitiesDelegate = UGameFrameworkComponentManager::FExtensionHandlerDelegate::CreateUObject(
|
|
this, &UGameFeatureAction_AddAbilities::HandleActorExtension, EntryIndex, ChangeContext);
|
|
TSharedPtr<FComponentRequestHandle> ExtensionRequestHandle = ComponentMan->AddExtensionHandler(Entry.ActorClass, AddAbilitiesDelegate);
|
|
|
|
ActiveData.ComponentRequests.Add(ExtensionRequestHandle);
|
|
EntryIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddAbilities::Reset(FPerContextData& ActiveData)
|
|
{
|
|
while (!ActiveData.ActiveExtensions.IsEmpty())
|
|
{
|
|
auto ExtensionIt = ActiveData.ActiveExtensions.CreateIterator();
|
|
RemoveActorAbilities(ExtensionIt->Key, ActiveData);
|
|
}
|
|
|
|
ActiveData.ComponentRequests.Empty();
|
|
}
|
|
|
|
void UGameFeatureAction_AddAbilities::HandleActorExtension(AActor* Actor, FName EventName, int32 EntryIndex, FGameFeatureStateChangeContext ChangeContext)
|
|
{
|
|
FPerContextData* ActiveData = ContextData.Find(ChangeContext);
|
|
if (AbilitiesList.IsValidIndex(EntryIndex) && ActiveData)
|
|
{
|
|
const FGameFeatureAbilitiesEntry& Entry = AbilitiesList[EntryIndex];
|
|
if ((EventName == UGameFrameworkComponentManager::NAME_ExtensionRemoved) || (EventName == UGameFrameworkComponentManager::NAME_ReceiverRemoved))
|
|
{
|
|
RemoveActorAbilities(Actor, *ActiveData);
|
|
}
|
|
else if ((EventName == UGameFrameworkComponentManager::NAME_ExtensionAdded) || (EventName == ALyraPlayerState::NAME_LyraAbilityReady))
|
|
{
|
|
AddActorAbilities(Actor, Entry, *ActiveData);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddAbilities::AddActorAbilities(AActor* Actor, const FGameFeatureAbilitiesEntry& AbilitiesEntry, FPerContextData& ActiveData)
|
|
{
|
|
check(Actor);
|
|
if (!Actor->HasAuthority())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// early out if Actor already has ability extensions applied
|
|
if (ActiveData.ActiveExtensions.Find(Actor) != nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (UAbilitySystemComponent* AbilitySystemComponent = FindOrAddComponentForActor<UAbilitySystemComponent>(Actor, AbilitiesEntry, ActiveData))
|
|
{
|
|
FActorExtensions AddedExtensions;
|
|
AddedExtensions.Abilities.Reserve(AbilitiesEntry.GrantedAbilities.Num());
|
|
AddedExtensions.Attributes.Reserve(AbilitiesEntry.GrantedAttributes.Num());
|
|
AddedExtensions.AbilitySetHandles.Reserve(AbilitiesEntry.GrantedAbilitySets.Num());
|
|
|
|
for (const FLyraAbilityGrant& Ability : AbilitiesEntry.GrantedAbilities)
|
|
{
|
|
if (!Ability.AbilityType.IsNull())
|
|
{
|
|
FGameplayAbilitySpec NewAbilitySpec(Ability.AbilityType.LoadSynchronous());
|
|
FGameplayAbilitySpecHandle AbilityHandle = AbilitySystemComponent->GiveAbility(NewAbilitySpec);
|
|
|
|
AddedExtensions.Abilities.Add(AbilityHandle);
|
|
}
|
|
}
|
|
|
|
for (const FLyraAttributeSetGrant& Attributes : AbilitiesEntry.GrantedAttributes)
|
|
{
|
|
if (!Attributes.AttributeSetType.IsNull())
|
|
{
|
|
TSubclassOf<UAttributeSet> SetType = Attributes.AttributeSetType.LoadSynchronous();
|
|
if (SetType)
|
|
{
|
|
UAttributeSet* NewSet = NewObject<UAttributeSet>(AbilitySystemComponent->GetOwner(), SetType);
|
|
if (!Attributes.InitializationData.IsNull())
|
|
{
|
|
UDataTable* InitData = Attributes.InitializationData.LoadSynchronous();
|
|
if (InitData)
|
|
{
|
|
NewSet->InitFromMetaDataTable(InitData);
|
|
}
|
|
}
|
|
|
|
AddedExtensions.Attributes.Add(NewSet);
|
|
AbilitySystemComponent->AddAttributeSetSubobject(NewSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
ULyraAbilitySystemComponent* LyraASC = CastChecked<ULyraAbilitySystemComponent>(AbilitySystemComponent);
|
|
for (const TSoftObjectPtr<const ULyraAbilitySet>& SetPtr : AbilitiesEntry.GrantedAbilitySets)
|
|
{
|
|
if (const ULyraAbilitySet* Set = SetPtr.Get())
|
|
{
|
|
Set->GiveToAbilitySystem(LyraASC, &AddedExtensions.AbilitySetHandles.AddDefaulted_GetRef());
|
|
}
|
|
}
|
|
|
|
ActiveData.ActiveExtensions.Add(Actor, AddedExtensions);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogGameFeatures, Error, TEXT("Failed to find/add an ability component to '%s'. Abilities will not be granted."), *Actor->GetPathName());
|
|
}
|
|
}
|
|
|
|
void UGameFeatureAction_AddAbilities::RemoveActorAbilities(AActor* Actor, FPerContextData& ActiveData)
|
|
{
|
|
if (FActorExtensions* ActorExtensions = ActiveData.ActiveExtensions.Find(Actor))
|
|
{
|
|
if (UAbilitySystemComponent* AbilitySystemComponent = Actor->FindComponentByClass<UAbilitySystemComponent>())
|
|
{
|
|
for (UAttributeSet* AttribSetInstance : ActorExtensions->Attributes)
|
|
{
|
|
AbilitySystemComponent->RemoveSpawnedAttribute(AttribSetInstance);
|
|
}
|
|
|
|
for (FGameplayAbilitySpecHandle AbilityHandle : ActorExtensions->Abilities)
|
|
{
|
|
AbilitySystemComponent->SetRemoveAbilityOnEnd(AbilityHandle);
|
|
}
|
|
|
|
ULyraAbilitySystemComponent* LyraASC = CastChecked<ULyraAbilitySystemComponent>(AbilitySystemComponent);
|
|
for (FLyraAbilitySet_GrantedHandles& SetHandle : ActorExtensions->AbilitySetHandles)
|
|
{
|
|
SetHandle.TakeFromAbilitySystem(LyraASC);
|
|
}
|
|
}
|
|
|
|
ActiveData.ActiveExtensions.Remove(Actor);
|
|
}
|
|
}
|
|
|
|
UActorComponent* UGameFeatureAction_AddAbilities::FindOrAddComponentForActor(UClass* ComponentType, AActor* Actor, const FGameFeatureAbilitiesEntry& AbilitiesEntry, FPerContextData& ActiveData)
|
|
{
|
|
UActorComponent* Component = Actor->FindComponentByClass(ComponentType);
|
|
|
|
bool bMakeComponentRequest = (Component == nullptr);
|
|
if (Component)
|
|
{
|
|
// Check to see if this component was created from a different `UGameFrameworkComponentManager` request.
|
|
// `Native` is what `CreationMethod` defaults to for dynamically added components.
|
|
if (Component->CreationMethod == EComponentCreationMethod::Native)
|
|
{
|
|
// Attempt to tell the difference between a true native component and one created by the GameFrameworkComponent system.
|
|
// If it is from the UGameFrameworkComponentManager, then we need to make another request (requests are ref counted).
|
|
UObject* ComponentArchetype = Component->GetArchetype();
|
|
bMakeComponentRequest = ComponentArchetype->HasAnyFlags(RF_ClassDefaultObject);
|
|
}
|
|
}
|
|
|
|
if (bMakeComponentRequest)
|
|
{
|
|
UWorld* World = Actor->GetWorld();
|
|
UGameInstance* GameInstance = World->GetGameInstance();
|
|
|
|
if (UGameFrameworkComponentManager* ComponentMan = UGameInstance::GetSubsystem<UGameFrameworkComponentManager>(GameInstance))
|
|
{
|
|
TSharedPtr<FComponentRequestHandle> RequestHandle = ComponentMan->AddComponentRequest(AbilitiesEntry.ActorClass, ComponentType);
|
|
ActiveData.ComponentRequests.Add(RequestHandle);
|
|
}
|
|
|
|
if (!Component)
|
|
{
|
|
Component = Actor->FindComponentByClass(ComponentType);
|
|
ensureAlways(Component);
|
|
}
|
|
}
|
|
|
|
return Component;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|