Files
PHY/Plugins/GCS/Source/GenericGameplayAbilities/Private/GGA_AbilitySystemComponent.cpp
2026-03-03 01:23:02 +08:00

592 lines
19 KiB
C++

// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_AbilitySystemComponent.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemLog.h"
#include "GameplayCueManager.h"
#include "GGA_AbilitySystemGlobals.h"
#include "GGA_AbilityTagRelationshipMapping.h"
#include "GGA_GlobalAbilitySystem.h"
#include "GGA_LogChannels.h"
#include "Abilities/GGA_GameplayAbilityInterface.h"
#include "GameFramework/Pawn.h"
#include "Runtime/Launch/Resources/Version.h"
#pragma region Initialization
UGGA_AbilitySystemComponent::UGGA_AbilitySystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
SetIsReplicatedByDefault(true);
AbilitySystemReplicationMode = EGameplayEffectReplicationMode::Mixed;
bReplicateUsingRegisteredSubObjectList = true;
FMemory::Memset(ActivationGroupCounts, 0, sizeof(ActivationGroupCounts));
}
void UGGA_AbilitySystemComponent::InitializeAbilitySystem(AActor* InOwnerActor, AActor* InAvatarActor)
{
check(InOwnerActor);
check(InAvatarActor);
if (!bAbilitySystemInitialized)
{
FGameplayAbilityActorInfo* ActorInfo = AbilityActorInfo.Get();
const bool AvatarChanged = InAvatarActor && (InAvatarActor != ActorInfo->AvatarActor);
InitAbilityActorInfo(InOwnerActor, InAvatarActor);
InitializeAbilitySets(InOwnerActor, InAvatarActor);
if (AttributeSetInitializeGroupName.IsValid())
{
InitializeAttributes(AttributeSetInitializeGroupName, AttributeSetInitializeLevel, true);
}
bAbilitySystemInitialized = true;
OnAbilitySystemInitialized.Broadcast();
}
}
void UGGA_AbilitySystemComponent::UninitializeAbilitySystem()
{
if (bAbilitySystemInitialized)
{
bAbilitySystemInitialized = false;
OnAbilitySystemUninitialized.Broadcast();
}
}
void UGGA_AbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor)
{
FGameplayAbilityActorInfo* ActorInfo = AbilityActorInfo.Get();
check(ActorInfo);
check(InOwnerActor);
const bool AvatarChanged = InAvatarActor && (InAvatarActor != ActorInfo->AvatarActor);
Super::InitAbilityActorInfo(InOwnerActor, InAvatarActor);
if (GetWorld() && !GetWorld()->IsGameWorld())
{
return;
}
if (AvatarChanged)
{
RegisterToGlobalAbilitySystem();
ABILITYLIST_SCOPE_LOCK();
for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items)
{
if (IGGA_GameplayAbilityInterface* AbilityCDO = Cast<IGGA_GameplayAbilityInterface>(AbilitySpec.Ability))
{
AbilityCDO->TryActivateAbilityOnSpawn(AbilityActorInfo.Get(), AbilitySpec);
}
}
}
}
void UGGA_AbilitySystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UnregisterToGlobalAbilitySystem();
Super::EndPlay(EndPlayReason);
}
void UGGA_AbilitySystemComponent::InitializeComponent()
{
SetReplicationMode(AbilitySystemReplicationMode);
Super::InitializeComponent();
}
void UGGA_AbilitySystemComponent::InitializeAbilitySets(AActor* InOwnerActor, AActor* InAvatarActor)
{
if (GetNetMode() != NM_Client)
{
for (int32 i = DefaultAbilitySet_GrantedHandles.Num() - 1; i >= 0; i--)
{
DefaultAbilitySet_GrantedHandles[i].TakeFromAbilitySystem(this);
}
DefaultAbilitySet_GrantedHandles.Empty();
for (TObjectPtr<const UGGA_AbilitySet> AbilitySet : DefaultAbilitySets)
{
if (!AbilitySet)
continue;
AbilitySet->GiveToAbilitySystem(this, /*inout*/ &DefaultAbilitySet_GrantedHandles.AddDefaulted_GetRef(), this);
}
}
}
void UGGA_AbilitySystemComponent::InitializeAttributes(FGGA_AttributeGroupName GroupName, int32 Level, bool bInitialInit)
{
if (const UGGA_AbilitySystemGlobals* Globals = Cast<UGGA_AbilitySystemGlobals>(UGGA_AbilitySystemGlobals::GetAbilitySystemGlobals()))
{
Globals->InitAttributeSetDefaults(this, GroupName, Level, bInitialInit);
}
else
{
UE_LOG(LogGGA_AbilitySystem, Warning, TEXT("Failed to InitializeAttributes as your project is not configured to use GGA_AbilitySystemGlobals(or derived class)."));
}
}
void UGGA_AbilitySystemComponent::SendGameplayEventToActor_Replicated(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
if (IsValid(Actor) && EventTag.IsValid())
{
if (Actor->HasAuthority())
{
MulticastSendGameplayEventToActor(Actor, EventTag, Payload);
}
else
{
ServerSendGameplayEventToActor(Actor, EventTag, Payload);
}
}
}
void UGGA_AbilitySystemComponent::ServerSendGameplayEventToActor_Implementation(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
MulticastSendGameplayEventToActor(Actor, EventTag, Payload);
}
bool UGGA_AbilitySystemComponent::ServerSendGameplayEventToActor_Validate(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
return true;
}
void UGGA_AbilitySystemComponent::MulticastSendGameplayEventToActor_Implementation(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(Actor, EventTag, Payload);
}
void UGGA_AbilitySystemComponent::PostInitProperties()
{
Super::PostInitProperties();
ReplicationMode = AbilitySystemReplicationMode;
}
void UGGA_AbilitySystemComponent::RegisterToGlobalAbilitySystem()
{
if (bRegisteredToGlobalAbilitySystem)
return;
// 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 (UGGA_GlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem<UGGA_GlobalAbilitySystem>(GetWorld()))
{
GlobalAbilitySystem->RegisterASC(this);
bRegisteredToGlobalAbilitySystem = true;
}
}
void UGGA_AbilitySystemComponent::UnregisterToGlobalAbilitySystem()
{
if (!bRegisteredToGlobalAbilitySystem)
return;
if (UGGA_GlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem<UGGA_GlobalAbilitySystem>(GetWorld()))
{
GlobalAbilitySystem->UnregisterASC(this);
bRegisteredToGlobalAbilitySystem = false;
}
}
#pragma endregion
#pragma region AbilitiesActivation
bool UGGA_AbilitySystemComponent::IsActivationGroupBlocked(EGGA_AbilityActivationGroup Group) const
{
bool bBlocked = false;
switch (Group)
{
case EGGA_AbilityActivationGroup::Independent:
// Independent abilities are never blocked.
bBlocked = false;
break;
case EGGA_AbilityActivationGroup::Exclusive_Replaceable:
case EGGA_AbilityActivationGroup::Exclusive_Blocking:
// Exclusive abilities can activate if nothing is blocking.
bBlocked = (ActivationGroupCounts[(uint8)EGGA_AbilityActivationGroup::Exclusive_Blocking] > 0);
break;
default:
checkf(false, TEXT("IsActivationGroupBlocked: Invalid ActivationGroup [%d]\n"), (uint8)Group);
break;
}
return bBlocked;
}
void UGGA_AbilitySystemComponent::AddAbilityToActivationGroup(EGGA_AbilityActivationGroup Group, UGameplayAbility* Ability)
{
check(Ability);
check(ActivationGroupCounts[(uint8)Group] < INT32_MAX);
ActivationGroupCounts[(uint8)Group]++;
const bool bReplicateCancelAbility = false;
switch (Group)
{
case EGGA_AbilityActivationGroup::Independent:
// Independent abilities do not cancel any other abilities.
break;
case EGGA_AbilityActivationGroup::Exclusive_Replaceable:
case EGGA_AbilityActivationGroup::Exclusive_Blocking:
CancelActivationGroupAbilities(EGGA_AbilityActivationGroup::Exclusive_Replaceable, Ability, bReplicateCancelAbility);
break;
default:
checkf(false, TEXT("AddAbilityToActivationGroup: In valid ActivationGroup [%d]\n"), (uint8)Group);
break;
}
const int32 ExclusiveCount = ActivationGroupCounts[(uint8)EGGA_AbilityActivationGroup::Exclusive_Replaceable] + ActivationGroupCounts[(uint8)EGGA_AbilityActivationGroup::Exclusive_Blocking];
if (!ensure(ExclusiveCount <= 1))
{
UE_LOG(LogGGA_AbilitySystem, Error, TEXT("AddAbilityToActivationGroup: Multiple exclusive abilities are running."));
}
}
void UGGA_AbilitySystemComponent::RemoveAbilityFromActivationGroup(EGGA_AbilityActivationGroup Group, UGameplayAbility* Ability)
{
check(Ability);
check(ActivationGroupCounts[(uint8)Group] > 0);
ActivationGroupCounts[(uint8)Group]--;
}
bool UGGA_AbilitySystemComponent::CanChangeActivationGroup(EGGA_AbilityActivationGroup NewGroup, UGameplayAbility* Ability) const
{
if (Ability == nullptr || !Ability->IsInstantiated() || !Ability->IsActive())
{
return false;
}
IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability);
if (AbilityInterface == nullptr)
{
return false;
}
if (AbilityInterface->GetActivationGroup() == NewGroup)
{
return true;
}
if ((AbilityInterface->GetActivationGroup() != EGGA_AbilityActivationGroup::Exclusive_Blocking) && IsActivationGroupBlocked(NewGroup))
{
// This ability can't change groups if it's blocked (unless it is the one doing the blocking).
return false;
}
if ((NewGroup == EGGA_AbilityActivationGroup::Exclusive_Replaceable) && !Ability->CanBeCanceled())
{
// This ability can't become replaceable if it can't be canceled.
return false;
}
return true;
}
bool UGGA_AbilitySystemComponent::ChangeActivationGroup(EGGA_AbilityActivationGroup NewGroup, UGameplayAbility* Ability)
{
if (!CanChangeActivationGroup(NewGroup, Ability))
{
return false;
}
IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability);
if (AbilityInterface == nullptr)
{
return false;
}
if (AbilityInterface->GetActivationGroup() != NewGroup)
{
RemoveAbilityFromActivationGroup(AbilityInterface->GetActivationGroup(), Ability);
AddAbilityToActivationGroup(NewGroup, Ability);
AbilityInterface->SetActivationGroup(NewGroup);
}
return true;
}
void UGGA_AbilitySystemComponent::NotifyAbilityActivated(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability)
{
Super::NotifyAbilityActivated(Handle, Ability);
if (IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability))
{
AddAbilityToActivationGroup(AbilityInterface->GetActivationGroup(), Ability);
}
OnAbilityActivated.Broadcast(Handle, Ability);
}
void UGGA_AbilitySystemComponent::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())
{
ClientNotifyAbilityActivationFailed(Ability, FailureReason);
return;
}
}
HandleAbilityActivationFailed(Ability, FailureReason);
}
void UGGA_AbilitySystemComponent::ClientNotifyAbilityActivationFailed_Implementation(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason)
{
HandleAbilityActivationFailed(Ability, FailureReason);
}
void UGGA_AbilitySystemComponent::HandleAbilityActivationFailed(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason)
{
OnAbilityActivationFailed.Broadcast(Ability, FailureReason);
if (const IGGA_GameplayAbilityInterface* AbilityInterface = Cast<const IGGA_GameplayAbilityInterface>(Ability))
{
AbilityInterface->HandleActivationFailed(FailureReason);
}
}
#pragma endregion
#pragma region AbilityCancellation
void UGGA_AbilitySystemComponent::CancelAbilitiesByFunc(TShouldCancelAbilityFunc ShouldCancelFunc, bool bReplicateCancelAbility)
{
ABILITYLIST_SCOPE_LOCK();
for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items)
{
if (AbilitySpec.Ability == nullptr || !AbilitySpec.IsActive())
{
continue;
}
#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION <5
if (AbilitySpec.Ability->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced)
#endif
{
// Cancel all the spawned instances, not the CDO.
TArray<UGameplayAbility*> Instances = AbilitySpec.GetAbilityInstances();
for (UGameplayAbility* AbilityInstance : Instances)
{
if (ShouldCancelFunc(AbilityInstance, AbilitySpec.Handle))
{
if (AbilityInstance->CanBeCanceled())
{
AbilityInstance->CancelAbility(AbilitySpec.Handle, AbilityActorInfo.Get(), AbilityInstance->GetCurrentActivationInfo(), bReplicateCancelAbility);
}
else
{
UE_LOG(LogGGA_AbilitySystem, Error, TEXT("CancelAbilitiesByFunc: Can't cancel ability [%s] because CanBeCanceled is false."), *AbilityInstance->GetName());
}
}
}
}
#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION <5
else
{
// Cancel the non-instanced ability CDO.
if (ShouldCancelFunc(AbilitySpec.Ability, AbilitySpec.Handle))
{
// Non-instanced abilities can always be canceled.
check(AbilitySpec.Ability->CanBeCanceled());
AbilitySpec.Ability->CancelAbility(AbilitySpec.Handle, AbilityActorInfo.Get(), FGameplayAbilityActivationInfo(), bReplicateCancelAbility);
}
}
#endif
}
}
void UGGA_AbilitySystemComponent::CancelActivationGroupAbilities(EGGA_AbilityActivationGroup Group, UGameplayAbility* IgnoreAbility, bool bReplicateCancelAbility)
{
auto ShouldCancelFunc = [this, Group, IgnoreAbility](const UGameplayAbility* Ability, FGameplayAbilitySpecHandle Handle)
{
bool SameGroup = false;
if (const IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability))
{
SameGroup = AbilityInterface->GetActivationGroup() == Group;
}
return (SameGroup && (Ability != IgnoreAbility));
};
CancelAbilitiesByFunc(ShouldCancelFunc, bReplicateCancelAbility);
}
void UGGA_AbilitySystemComponent::NotifyAbilityEnded(FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, bool bWasCancelled)
{
Super::NotifyAbilityEnded(Handle, Ability, bWasCancelled);
if (const IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability))
{
RemoveAbilityFromActivationGroup(AbilityInterface->GetActivationGroup(), Ability);
}
AbilityEndedEvent.Broadcast(Handle, Ability, bWasCancelled);
}
void UGGA_AbilitySystemComponent::HandleChangeAbilityCanBeCanceled(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bCanBeCanceled)
{
Super::HandleChangeAbilityCanBeCanceled(AbilityTags, RequestingAbility, bCanBeCanceled);
//@TODO: Apply any special logic like blocking input or movement
}
#pragma endregion
#pragma region Abilities
bool UGGA_AbilitySystemComponent::GetCooldownRemainingForTags(FGameplayTagContainer CooldownTags, float& TimeRemaining, float& CooldownDuration)
{
if (CooldownTags.Num() > 0)
{
TimeRemaining = 0.f;
CooldownDuration = 0.f;
FGameplayEffectQuery const Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(CooldownTags);
TArray<TPair<float, float>> DurationAndTimeRemaining = GetActiveEffectsTimeRemainingAndDuration(Query);
if (DurationAndTimeRemaining.Num() > 0)
{
int32 BestIdx = 0;
float LongestTime = DurationAndTimeRemaining[0].Key;
for (int32 Idx = 1; Idx < DurationAndTimeRemaining.Num(); ++Idx)
{
if (DurationAndTimeRemaining[Idx].Key > LongestTime)
{
LongestTime = DurationAndTimeRemaining[Idx].Key;
BestIdx = Idx;
}
}
TimeRemaining = DurationAndTimeRemaining[BestIdx].Key;
CooldownDuration = DurationAndTimeRemaining[BestIdx].Value;
return true;
}
}
return false;
}
bool UGGA_AbilitySystemComponent::BatchRPCTryActivateAbility(FGameplayAbilitySpecHandle InAbilityHandle,
bool EndAbilityImmediately)
{
bool AbilityActivated = false;
if (InAbilityHandle.IsValid())
{
FScopedServerAbilityRPCBatcher AbilityRpcBatching(this, InAbilityHandle);
AbilityActivated = TryActivateAbility(InAbilityHandle, true);
if (EndAbilityImmediately)
{
FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(InAbilityHandle);
if (AbilitySpec)
{
if (IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(AbilitySpec->GetPrimaryInstance()))
{
AbilityInterface->ExternalEndAbility();
}
}
}
return AbilityActivated;
}
return AbilityActivated;
}
#pragma endregion
#pragma region GameplayTags
void UGGA_AbilitySystemComponent::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
{
TagContainer.Reset(); // Fix for Version under 5.2
TagContainer.AppendTags(GameplayTagCountContainer.GetExplicitGameplayTags());
}
FString UGGA_AbilitySystemComponent::GetOwnedGameplayTagsString()
{
FString BlockedTagsStrings;
for (auto Tag : GameplayTagCountContainer.GetExplicitGameplayTags())
{
BlockedTagsStrings.Append(FString::Printf(TEXT("%s (%d),\n"), *Tag.ToString(), GameplayTagCountContainer.GetTagCount(Tag)));
}
return BlockedTagsStrings;
}
void UGGA_AbilitySystemComponent::GetAdditionalActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired,
FGameplayTagContainer& OutActivationBlocked) const
{
if (TagRelationshipMapping)
{
FGameplayTagContainer ActorTags;
GetOwnedGameplayTags(ActorTags);
TagRelationshipMapping->GetRequiredAndBlockedActivationTagsV2(ActorTags, AbilityTags, &OutActivationRequired, &OutActivationBlocked);
// TagRelationshipMapping->GetRequiredAndBlockedActivationTags(AbilityTags, &OutActivationRequired, &OutActivationBlocked);
}
}
void UGGA_AbilitySystemComponent::ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags,
const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, const FGameplayTagContainer& CancelTags)
{
FGameplayTagContainer ModifiedBlockTags = BlockTags;
FGameplayTagContainer ModifiedCancelTags = CancelTags;
if (TagRelationshipMapping)
{
FGameplayTagContainer ActorTags;
GetOwnedGameplayTags(ActorTags);
// Use the mapping to expand the ability tags into block and cancel tag
TagRelationshipMapping->GetAbilityTagsToBlockAndCancelV2(ActorTags, AbilityTags, &ModifiedBlockTags, &ModifiedCancelTags);
// TagRelationshipMapping->GetAbilityTagsToBlockAndCancel(AbilityTags, &ModifiedBlockTags, &ModifiedCancelTags);
}
Super::ApplyAbilityBlockAndCancelTags(AbilityTags, RequestingAbility, bEnableBlockTags, ModifiedBlockTags, bExecuteCancelTags, ModifiedCancelTags);
//@TODO: Apply any special logic like blocking input or movement
}
void UGGA_AbilitySystemComponent::SetTagRelationshipMapping(UGGA_AbilityTagRelationshipMapping* NewMapping)
{
TagRelationshipMapping = NewMapping;
}
#pragma endregion
#pragma region Attributes
FString UGGA_AbilitySystemComponent::GetOwnedGameplayAttributeSetString()
{
FString AttributeSetString;
TArray<FGameplayAttribute> Attributes;
GetAllAttributes(Attributes);
for (const auto& Attribute : Attributes)
{
AttributeSetString.Append(
FString::Printf(TEXT("%s : %.2f \n"), *Attribute.GetName(), GetNumericAttribute(Attribute)));
}
return AttributeSetString;
}
#pragma endregion
#pragma region TargetData
void UGGA_AbilitySystemComponent::GetAbilityTargetData(const FGameplayAbilitySpecHandle AbilityHandle, FGameplayAbilityActivationInfo ActivationInfo,
FGameplayAbilityTargetDataHandle& OutTargetDataHandle)
{
TSharedPtr<FAbilityReplicatedDataCache> ReplicatedData = AbilityTargetDataMap.Find(FGameplayAbilitySpecHandleAndPredictionKey(AbilityHandle, ActivationInfo.GetActivationPredictionKey()));
if (ReplicatedData.IsValid())
{
OutTargetDataHandle = ReplicatedData->TargetData;
}
}
#pragma endregion