RealtimeStyleTransferRuntime/Source/LyraGame/AbilitySystem/LyraAbilitySystemComponent.cpp

541 lines
19 KiB
C++

// 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<ULyraGlobalAbilitySystem>(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<APawn>(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<ULyraGameplayAbility>(AbilitySpec.Ability);
if (LyraAbilityCDO->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced)
{
TArray<UGameplayAbility*> Instances = AbilitySpec.GetAbilityInstances();
for (UGameplayAbility* AbilityInstance : Instances)
{
ULyraGameplayAbility* LyraAbilityInstance = CastChecked<ULyraGameplayAbility>(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<ULyraGlobalAbilitySystem>(GetWorld()))
{
GlobalAbilitySystem->RegisterASC(this);
}
if (ULyraAnimInstance* LyraAnimInst = Cast<ULyraAnimInstance>(ActorInfo->GetAnimInstance()))
{
LyraAnimInst->InitializeWithAbilitySystem(this);
}
TryActivateAbilitiesOnSpawn();
}
}
void ULyraAbilitySystemComponent::TryActivateAbilitiesOnSpawn()
{
ABILITYLIST_SCOPE_LOCK();
for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items)
{
const ULyraGameplayAbility* LyraAbilityCDO = CastChecked<ULyraGameplayAbility>(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<ULyraGameplayAbility>(AbilitySpec.Ability);
if (LyraAbilityCDO->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced)
{
// Cancel all the spawned instances, not the CDO.
TArray<UGameplayAbility*> Instances = AbilitySpec.GetAbilityInstances();
for (UGameplayAbility* AbilityInstance : Instances)
{
ULyraGameplayAbility* LyraAbilityInstance = CastChecked<ULyraGameplayAbility>(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<FGameplayAbilitySpecHandle> 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<ULyraGameplayAbility>(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<ULyraGameplayAbility>(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<ULyraGameplayAbility>(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<APawn>(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<ULyraGameplayAbility>(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<const ULyraGameplayAbility>(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<UGameplayEffect> 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<UGameplayEffect> 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<FAbilityReplicatedDataCache> ReplicatedData = AbilityTargetDataMap.Find(FGameplayAbilitySpecHandleAndPredictionKey(AbilityHandle, ActivationInfo.GetActivationPredictionKey()));
if (ReplicatedData.IsValid())
{
OutTargetDataHandle = ReplicatedData->TargetData;
}
}