// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraAbilitySystemComponent.h" #include "Abilities/GameplayAbility.h" #include "Abilities/GameplayAbilityTargetTypes.h" #include "Abilities/GameplayAbilityTypes.h" #include "Abilities/LyraGameplayAbility.h" #include "AbilitySystem/LyraAbilityTagRelationshipMapping.h" #include "Animation/LyraAnimInstance.h" #include "Containers/UnrealString.h" #include "Engine/World.h" #include "GameFramework/Actor.h" #include "GameFramework/Pawn.h" #include "GameplayAbilitySpec.h" #include "GameplayEffect.h" #include "GameplayEffectTypes.h" #include "GameplayTagContainer.h" #include "HAL/PlatformMath.h" #include "HAL/UnrealMemory.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "LyraGlobalAbilitySystem.h" #include "LyraLogChannels.h" #include "Misc/AssertionMacros.h" #include "System/LyraAssetManager.h" #include "System/LyraGameData.h" #include "Templates/Casts.h" #include "Templates/SharedPointer.h" #include "Templates/SubclassOf.h" #include "Trace/Detail/Channel.h" #include "UObject/NameTypes.h" #include "UObject/SoftObjectPtr.h" #include "UObject/UObjectBaseUtility.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_AbilityInputBlocked, "Gameplay.AbilityInputBlocked"); ULyraAbilitySystemComponent::ULyraAbilitySystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { InputPressedSpecHandles.Reset(); InputReleasedSpecHandles.Reset(); InputHeldSpecHandles.Reset(); FMemory::Memset(ActivationGroupCounts, 0, sizeof(ActivationGroupCounts)); } void ULyraAbilitySystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (ULyraGlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem(GetWorld())) { GlobalAbilitySystem->UnregisterASC(this); } Super::EndPlay(EndPlayReason); } void ULyraAbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor) { FGameplayAbilityActorInfo* ActorInfo = AbilityActorInfo.Get(); check(ActorInfo); check(InOwnerActor); const bool bHasNewPawnAvatar = Cast(InAvatarActor) && (InAvatarActor != ActorInfo->AvatarActor); Super::InitAbilityActorInfo(InOwnerActor, InAvatarActor); if (bHasNewPawnAvatar) { // Notify all abilities that a new pawn avatar has been set for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) { ULyraGameplayAbility* LyraAbilityCDO = CastChecked(AbilitySpec.Ability); if (LyraAbilityCDO->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced) { TArray Instances = AbilitySpec.GetAbilityInstances(); for (UGameplayAbility* AbilityInstance : Instances) { ULyraGameplayAbility* LyraAbilityInstance = CastChecked(AbilityInstance); LyraAbilityInstance->OnPawnAvatarSet(); } } else { LyraAbilityCDO->OnPawnAvatarSet(); } } // Register with the global system once we actually have a pawn avatar. We wait until this time since some globally-applied effects may require an avatar. if (ULyraGlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem(GetWorld())) { GlobalAbilitySystem->RegisterASC(this); } if (ULyraAnimInstance* LyraAnimInst = Cast(ActorInfo->GetAnimInstance())) { LyraAnimInst->InitializeWithAbilitySystem(this); } TryActivateAbilitiesOnSpawn(); } } void ULyraAbilitySystemComponent::TryActivateAbilitiesOnSpawn() { ABILITYLIST_SCOPE_LOCK(); for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) { const ULyraGameplayAbility* LyraAbilityCDO = CastChecked(AbilitySpec.Ability); LyraAbilityCDO->TryActivateAbilityOnSpawn(AbilityActorInfo.Get(), AbilitySpec); } } void ULyraAbilitySystemComponent::CancelAbilitiesByFunc(TShouldCancelAbilityFunc ShouldCancelFunc, bool bReplicateCancelAbility) { ABILITYLIST_SCOPE_LOCK(); for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) { if (!AbilitySpec.IsActive()) { continue; } ULyraGameplayAbility* LyraAbilityCDO = CastChecked(AbilitySpec.Ability); if (LyraAbilityCDO->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced) { // Cancel all the spawned instances, not the CDO. TArray Instances = AbilitySpec.GetAbilityInstances(); for (UGameplayAbility* AbilityInstance : Instances) { ULyraGameplayAbility* LyraAbilityInstance = CastChecked(AbilityInstance); if (ShouldCancelFunc(LyraAbilityInstance, AbilitySpec.Handle)) { if (LyraAbilityInstance->CanBeCanceled()) { LyraAbilityInstance->CancelAbility(AbilitySpec.Handle, AbilityActorInfo.Get(), LyraAbilityInstance->GetCurrentActivationInfo(), bReplicateCancelAbility); } else { UE_LOG(LogLyraAbilitySystem, Error, TEXT("CancelAbilitiesByFunc: Can't cancel ability [%s] because CanBeCanceled is false."), *LyraAbilityInstance->GetName()); } } } } else { // Cancel the non-instanced ability CDO. if (ShouldCancelFunc(LyraAbilityCDO, AbilitySpec.Handle)) { // Non-instanced abilities can always be canceled. check(LyraAbilityCDO->CanBeCanceled()); LyraAbilityCDO->CancelAbility(AbilitySpec.Handle, AbilityActorInfo.Get(), FGameplayAbilityActivationInfo(), bReplicateCancelAbility); } } } } void ULyraAbilitySystemComponent::CancelInputActivatedAbilities(bool bReplicateCancelAbility) { auto ShouldCancelFunc = [this](const ULyraGameplayAbility* LyraAbility, FGameplayAbilitySpecHandle Handle) { const ELyraAbilityActivationPolicy ActivationPolicy = LyraAbility->GetActivationPolicy(); return ((ActivationPolicy == ELyraAbilityActivationPolicy::OnInputTriggered) || (ActivationPolicy == ELyraAbilityActivationPolicy::WhileInputActive)); }; CancelAbilitiesByFunc(ShouldCancelFunc, bReplicateCancelAbility); } void ULyraAbilitySystemComponent::AbilitySpecInputPressed(FGameplayAbilitySpec& Spec) { Super::AbilitySpecInputPressed(Spec); // We don't support UGameplayAbility::bReplicateInputDirectly. // Use replicated events instead so that the WaitInputPress ability task works. if (Spec.IsActive()) { // Invoke the InputPressed event. This is not replicated here. If someone is listening, they may replicate the InputPressed event to the server. InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, Spec.Handle, Spec.ActivationInfo.GetActivationPredictionKey()); } } void ULyraAbilitySystemComponent::AbilitySpecInputReleased(FGameplayAbilitySpec& Spec) { Super::AbilitySpecInputReleased(Spec); // We don't support UGameplayAbility::bReplicateInputDirectly. // Use replicated events instead so that the WaitInputRelease ability task works. if (Spec.IsActive()) { // Invoke the InputReleased event. This is not replicated here. If someone is listening, they may replicate the InputReleased event to the server. InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputReleased, Spec.Handle, Spec.ActivationInfo.GetActivationPredictionKey()); } } void ULyraAbilitySystemComponent::AbilityInputTagPressed(const FGameplayTag& InputTag) { if (InputTag.IsValid()) { for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) { if (AbilitySpec.Ability && (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))) { InputPressedSpecHandles.AddUnique(AbilitySpec.Handle); InputHeldSpecHandles.AddUnique(AbilitySpec.Handle); } } } } void ULyraAbilitySystemComponent::AbilityInputTagReleased(const FGameplayTag& InputTag) { if (InputTag.IsValid()) { for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) { if (AbilitySpec.Ability && (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))) { InputReleasedSpecHandles.AddUnique(AbilitySpec.Handle); InputHeldSpecHandles.Remove(AbilitySpec.Handle); } } } } void ULyraAbilitySystemComponent::ProcessAbilityInput(float DeltaTime, bool bGamePaused) { if (HasMatchingGameplayTag(TAG_Gameplay_AbilityInputBlocked)) { ClearAbilityInput(); return; } static TArray AbilitiesToActivate; AbilitiesToActivate.Reset(); //@TODO: See if we can use FScopedServerAbilityRPCBatcher ScopedRPCBatcher in some of these loops // // Process all abilities that activate when the input is held. // for (const FGameplayAbilitySpecHandle& SpecHandle : InputHeldSpecHandles) { if (const FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(SpecHandle)) { if (AbilitySpec->Ability && !AbilitySpec->IsActive()) { const ULyraGameplayAbility* LyraAbilityCDO = CastChecked(AbilitySpec->Ability); if (LyraAbilityCDO->GetActivationPolicy() == ELyraAbilityActivationPolicy::WhileInputActive) { AbilitiesToActivate.AddUnique(AbilitySpec->Handle); } } } } // // Process all abilities that had their input pressed this frame. // for (const FGameplayAbilitySpecHandle& SpecHandle : InputPressedSpecHandles) { if (FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(SpecHandle)) { if (AbilitySpec->Ability) { AbilitySpec->InputPressed = true; if (AbilitySpec->IsActive()) { // Ability is active so pass along the input event. AbilitySpecInputPressed(*AbilitySpec); } else { const ULyraGameplayAbility* LyraAbilityCDO = CastChecked(AbilitySpec->Ability); if (LyraAbilityCDO->GetActivationPolicy() == ELyraAbilityActivationPolicy::OnInputTriggered) { AbilitiesToActivate.AddUnique(AbilitySpec->Handle); } } } } } // // Try to activate all the abilities that are from presses and holds. // We do it all at once so that held inputs don't activate the ability // and then also send a input event to the ability because of the press. // for (const FGameplayAbilitySpecHandle& AbilitySpecHandle : AbilitiesToActivate) { TryActivateAbility(AbilitySpecHandle); } // // Process all abilities that had their input released this frame. // for (const FGameplayAbilitySpecHandle& SpecHandle : InputReleasedSpecHandles) { if (FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(SpecHandle)) { if (AbilitySpec->Ability) { AbilitySpec->InputPressed = false; if (AbilitySpec->IsActive()) { // Ability is active so pass along the input event. AbilitySpecInputReleased(*AbilitySpec); } } } } // // Clear the cached ability handles. // InputPressedSpecHandles.Reset(); InputReleasedSpecHandles.Reset(); } void ULyraAbilitySystemComponent::ClearAbilityInput() { InputPressedSpecHandles.Reset(); InputReleasedSpecHandles.Reset(); InputHeldSpecHandles.Reset(); } void ULyraAbilitySystemComponent::NotifyAbilityActivated(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability) { Super::NotifyAbilityActivated(Handle, Ability); ULyraGameplayAbility* LyraAbility = CastChecked(Ability); AddAbilityToActivationGroup(LyraAbility->GetActivationGroup(), LyraAbility); } void ULyraAbilitySystemComponent::NotifyAbilityFailed(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason) { Super::NotifyAbilityFailed(Handle, Ability, FailureReason); if (APawn* Avatar = Cast(GetAvatarActor())) { if (!Avatar->IsLocallyControlled() && Ability->IsSupportedForNetworking()) { ClientNotifyAbilityFailed(Ability, FailureReason); return; } } HandleAbilityFailed(Ability, FailureReason); } void ULyraAbilitySystemComponent::NotifyAbilityEnded(FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, bool bWasCancelled) { Super::NotifyAbilityEnded(Handle, Ability, bWasCancelled); ULyraGameplayAbility* LyraAbility = CastChecked(Ability); RemoveAbilityFromActivationGroup(LyraAbility->GetActivationGroup(), LyraAbility); } void ULyraAbilitySystemComponent::ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags, const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, const FGameplayTagContainer& CancelTags) { FGameplayTagContainer ModifiedBlockTags = BlockTags; FGameplayTagContainer ModifiedCancelTags = CancelTags; if (TagRelationshipMapping) { // Use the mapping to expand the ability tags into block and cancel tag TagRelationshipMapping->GetAbilityTagsToBlockAndCancel(AbilityTags, &ModifiedBlockTags, &ModifiedCancelTags); } Super::ApplyAbilityBlockAndCancelTags(AbilityTags, RequestingAbility, bEnableBlockTags, ModifiedBlockTags, bExecuteCancelTags, ModifiedCancelTags); //@TODO: Apply any special logic like blocking input or movement } void ULyraAbilitySystemComponent::HandleChangeAbilityCanBeCanceled(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bCanBeCanceled) { Super::HandleChangeAbilityCanBeCanceled(AbilityTags, RequestingAbility, bCanBeCanceled); //@TODO: Apply any special logic like blocking input or movement } void ULyraAbilitySystemComponent::GetAdditionalActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired, FGameplayTagContainer& OutActivationBlocked) const { if (TagRelationshipMapping) { TagRelationshipMapping->GetRequiredAndBlockedActivationTags(AbilityTags, &OutActivationRequired, &OutActivationBlocked); } } void ULyraAbilitySystemComponent::SetTagRelationshipMapping(ULyraAbilityTagRelationshipMapping* NewMapping) { TagRelationshipMapping = NewMapping; } void ULyraAbilitySystemComponent::ClientNotifyAbilityFailed_Implementation(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason) { HandleAbilityFailed(Ability, FailureReason); } void ULyraAbilitySystemComponent::HandleAbilityFailed(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason) { //UE_LOG(LogLyraAbilitySystem, Warning, TEXT("Ability %s failed to activate (tags: %s)"), *GetPathNameSafe(Ability), *FailureReason.ToString()); if (const ULyraGameplayAbility* LyraAbility = Cast(Ability)) { LyraAbility->OnAbilityFailedToActivate(FailureReason); } } bool ULyraAbilitySystemComponent::IsActivationGroupBlocked(ELyraAbilityActivationGroup Group) const { bool bBlocked = false; switch (Group) { case ELyraAbilityActivationGroup::Independent: // Independent abilities are never blocked. bBlocked = false; break; case ELyraAbilityActivationGroup::Exclusive_Replaceable: case ELyraAbilityActivationGroup::Exclusive_Blocking: // Exclusive abilities can activate if nothing is blocking. bBlocked = (ActivationGroupCounts[(uint8)ELyraAbilityActivationGroup::Exclusive_Blocking] > 0); break; default: checkf(false, TEXT("IsActivationGroupBlocked: Invalid ActivationGroup [%d]\n"), (uint8)Group); break; } return bBlocked; } void ULyraAbilitySystemComponent::AddAbilityToActivationGroup(ELyraAbilityActivationGroup Group, ULyraGameplayAbility* LyraAbility) { check(LyraAbility); check(ActivationGroupCounts[(uint8)Group] < INT32_MAX); ActivationGroupCounts[(uint8)Group]++; const bool bReplicateCancelAbility = false; switch (Group) { case ELyraAbilityActivationGroup::Independent: // Independent abilities do not cancel any other abilities. break; case ELyraAbilityActivationGroup::Exclusive_Replaceable: case ELyraAbilityActivationGroup::Exclusive_Blocking: CancelActivationGroupAbilities(ELyraAbilityActivationGroup::Exclusive_Replaceable, LyraAbility, bReplicateCancelAbility); break; default: checkf(false, TEXT("AddAbilityToActivationGroup: Invalid ActivationGroup [%d]\n"), (uint8)Group); break; } const int32 ExclusiveCount = ActivationGroupCounts[(uint8)ELyraAbilityActivationGroup::Exclusive_Replaceable] + ActivationGroupCounts[(uint8)ELyraAbilityActivationGroup::Exclusive_Blocking]; if (!ensure(ExclusiveCount <= 1)) { UE_LOG(LogLyraAbilitySystem, Error, TEXT("AddAbilityToActivationGroup: Multiple exclusive abilities are running.")); } } void ULyraAbilitySystemComponent::RemoveAbilityFromActivationGroup(ELyraAbilityActivationGroup Group, ULyraGameplayAbility* LyraAbility) { check(LyraAbility); check(ActivationGroupCounts[(uint8)Group] > 0); ActivationGroupCounts[(uint8)Group]--; } void ULyraAbilitySystemComponent::CancelActivationGroupAbilities(ELyraAbilityActivationGroup Group, ULyraGameplayAbility* IgnoreLyraAbility, bool bReplicateCancelAbility) { auto ShouldCancelFunc = [this, Group, IgnoreLyraAbility](const ULyraGameplayAbility* LyraAbility, FGameplayAbilitySpecHandle Handle) { return ((LyraAbility->GetActivationGroup() == Group) && (LyraAbility != IgnoreLyraAbility)); }; CancelAbilitiesByFunc(ShouldCancelFunc, bReplicateCancelAbility); } void ULyraAbilitySystemComponent::AddDynamicTagGameplayEffect(const FGameplayTag& Tag) { const TSubclassOf DynamicTagGE = ULyraAssetManager::GetSubclass(ULyraGameData::Get().DynamicTagGameplayEffect); if (!DynamicTagGE) { UE_LOG(LogLyraAbilitySystem, Warning, TEXT("AddDynamicTagGameplayEffect: Unable to find DynamicTagGameplayEffect [%s]."), *ULyraGameData::Get().DynamicTagGameplayEffect.GetAssetName()); return; } const FGameplayEffectSpecHandle SpecHandle = MakeOutgoingSpec(DynamicTagGE, 1.0f, MakeEffectContext()); FGameplayEffectSpec* Spec = SpecHandle.Data.Get(); if (!Spec) { UE_LOG(LogLyraAbilitySystem, Warning, TEXT("AddDynamicTagGameplayEffect: Unable to make outgoing spec for [%s]."), *GetNameSafe(DynamicTagGE)); return; } Spec->DynamicGrantedTags.AddTag(Tag); ApplyGameplayEffectSpecToSelf(*Spec); } void ULyraAbilitySystemComponent::RemoveDynamicTagGameplayEffect(const FGameplayTag& Tag) { const TSubclassOf DynamicTagGE = ULyraAssetManager::GetSubclass(ULyraGameData::Get().DynamicTagGameplayEffect); if (!DynamicTagGE) { UE_LOG(LogLyraAbilitySystem, Warning, TEXT("RemoveDynamicTagGameplayEffect: Unable to find gameplay effect [%s]."), *ULyraGameData::Get().DynamicTagGameplayEffect.GetAssetName()); return; } FGameplayEffectQuery Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(FGameplayTagContainer(Tag)); Query.EffectDefinition = DynamicTagGE; RemoveActiveEffects(Query); } void ULyraAbilitySystemComponent::GetAbilityTargetData(const FGameplayAbilitySpecHandle AbilityHandle, FGameplayAbilityActivationInfo ActivationInfo, FGameplayAbilityTargetDataHandle& OutTargetDataHandle) { TSharedPtr ReplicatedData = AbilityTargetDataMap.Find(FGameplayAbilitySpecHandleAndPredictionKey(AbilityHandle, ActivationInfo.GetActivationPredictionKey())); if (ReplicatedData.IsValid()) { OutTargetDataHandle = ReplicatedData->TargetData; } }