// 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& 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& 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(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 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(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 SetType = Attributes.AttributeSetType.LoadSynchronous(); if (SetType) { UAttributeSet* NewSet = NewObject(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(AbilitySystemComponent); for (const TSoftObjectPtr& 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()) { for (UAttributeSet* AttribSetInstance : ActorExtensions->Attributes) { AbilitySystemComponent->RemoveSpawnedAttribute(AttribSetInstance); } for (FGameplayAbilitySpecHandle AbilityHandle : ActorExtensions->Abilities) { AbilitySystemComponent->SetRemoveAbilityOnEnd(AbilityHandle); } ULyraAbilitySystemComponent* LyraASC = CastChecked(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(GameInstance)) { TSharedPtr RequestHandle = ComponentMan->AddComponentRequest(AbilitiesEntry.ActorClass, ComponentType); ActiveData.ComponentRequests.Add(RequestHandle); } if (!Component) { Component = Actor->FindComponentByClass(ComponentType); ensureAlways(Component); } } return Component; } #undef LOCTEXT_NAMESPACE