// Copyright 2025 https://yuewu.dev/en All Rights Reserved. #include "Abilities/GGA_GameplayAbility.h" #include "AbilitySystemBlueprintLibrary.h" #include "AbilitySystemGlobals.h" #include "AbilitySystemLog.h" #include "Runtime/Launch/Resources/Version.h" #include "Abilities/GGA_AbilityCost.h" #include "GGA_AbilitySystemComponent.h" #include "GGA_GameplayTags.h" #include "GameFramework/PlayerController.h" #include "GameFramework/Controller.h" #include "GameFramework/Pawn.h" #include "GGA_LogChannels.h" #include "Misc/DataValidation.h" #include "Utilities/GGA_GameplayEffectContainerFunctionLibrary.h" #define ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(FunctionName, ReturnValue) \ { \ if (!ensure(IsInstantiated())) \ { \ ABILITY_LOG(Error, TEXT("%s: " #FunctionName " cannot be called on a non-instanced ability. Check the instancing policy."), *GetPathName()); \ return ReturnValue; \ } \ } UGGA_GameplayAbility::UGGA_GameplayAbility(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo; bServerRespectsRemoteAbilityCancellation = false; InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor; NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted; NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer; ActivationGroup = EGGA_AbilityActivationGroup::Independent; bReplicateInputDirectly = false; bEnableTick = false; } void UGGA_GameplayAbility::Tick(float DeltaTime) { AbilityTick(DeltaTime); } TStatId UGGA_GameplayAbility::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(UGGA_GameplayAbility, STATGROUP_GameplayAbility); } bool UGGA_GameplayAbility::IsTickable() const { return IsInstantiated() && bEnableTick && GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerActor && IsActive(); } void UGGA_GameplayAbility::AbilityTick_Implementation(float DeltaTime) { } AController* UGGA_GameplayAbility::GetControllerFromActorInfo() const { if (CurrentActorInfo) { if (AController* PC = CurrentActorInfo->PlayerController.Get()) { return PC; } // Look for a player controller or pawn in the owner chain. AActor* TestActor = CurrentActorInfo->OwnerActor.Get(); while (TestActor) { if (AController* C = Cast(TestActor)) { return C; } if (APawn* Pawn = Cast(TestActor)) { return Pawn->GetController(); } TestActor = TestActor->GetOwner(); } } return nullptr; } void UGGA_GameplayAbility::SetActivationGroup(EGGA_AbilityActivationGroup NewGroup) { ActivationGroup = NewGroup; } void UGGA_GameplayAbility::TryActivateAbilityOnSpawn(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) const { PRAGMA_DISABLE_DEPRECATION_WARNINGS #if ENGINE_MINOR_VERSION > 4 // Fixing this up to use the instance activation, but this function should be deprecated as it cannot work with InstancedPerExecution UE_CLOG(Spec.Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerExecution, LogAbilitySystem, Warning, TEXT("%hs: %s is InstancedPerExecution. This is unreliable for Input as you may only interact with the latest spawned Instance"), __func__, *GetNameSafe(Spec.Ability)); TArray Instances = Spec.GetAbilityInstances(); const FGameplayAbilityActivationInfo& ActivationInfo = Instances.IsEmpty() ? Spec.ActivationInfo : Instances.Last()->GetCurrentActivationInfoRef(); const bool bIsPredicting = (ActivationInfo.ActivationMode == EGameplayAbilityActivationMode::Predicting); #else const bool bIsPredicting = (Spec.ActivationInfo.ActivationMode == EGameplayAbilityActivationMode::Predicting); #endif PRAGMA_ENABLE_DEPRECATION_WARNINGS // Try to activate if activation policy is on spawn. #if ENGINE_MINOR_VERSION > 4 if (ActorInfo && !Spec.IsActive() && !bIsPredicting && GetAssetTags().HasTagExact(GGA_AbilityTraitTags::ActivationOnSpawn)) #else if (ActorInfo && !Spec.IsActive() && !bIsPredicting && AbilityTags.HasTagExact(GGA_AbilityTraitTags::ActivationOnSpawn)) #endif { UAbilitySystemComponent* ASC = ActorInfo->AbilitySystemComponent.Get(); const AActor* AvatarActor = ActorInfo->AvatarActor.Get(); // If avatar actor is torn off or about to die, don't try to activate until we get the new one. if (ASC && AvatarActor && !AvatarActor->GetTearOff() && (AvatarActor->GetLifeSpan() <= 0.0f)) { const bool bIsLocalExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalPredicted) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalOnly); const bool bIsServerExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerOnly) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerInitiated); const bool bClientShouldActivate = ActorInfo->IsLocallyControlled() && bIsLocalExecution; const bool bServerShouldActivate = ActorInfo->IsNetAuthority() && bIsServerExecution; if (bClientShouldActivate || bServerShouldActivate) { ASC->TryActivateAbility(Spec.Handle); } } } } void UGGA_GameplayAbility::HandleActivationFailed(const FGameplayTagContainer& FailedReason) const { OnActivationFailed(FailedReason); } bool UGGA_GameplayAbility::HasEffectContainer(FGameplayTag ContainerTag) { return EffectContainerMap.Contains(ContainerTag); } FGGA_GameplayEffectContainerSpec UGGA_GameplayAbility::MakeEffectContainerSpec(FGameplayTag ContainerTag, const FGameplayEventData& EventData, int32 OverrideGameplayLevel) { FGGA_GameplayEffectContainer* FoundContainer = EffectContainerMap.Find(ContainerTag); if (FoundContainer) { return UGGA_GameplayEffectContainerFunctionLibrary::MakeEffectContainerSpec(*FoundContainer, EventData, OverrideGameplayLevel, this); } return FGGA_GameplayEffectContainerSpec(); } TArray UGGA_GameplayAbility::ApplyEffectContainer(FGameplayTag ContainerTag, const FGameplayEventData& EventData, int32 OverrideGameplayLevel) { FGGA_GameplayEffectContainer* FoundContainer = EffectContainerMap.Find(ContainerTag); if (FoundContainer) { const FGGA_GameplayEffectContainerSpec Spec = UGGA_GameplayEffectContainerFunctionLibrary::MakeEffectContainerSpec(*FoundContainer, EventData, OverrideGameplayLevel, this); return UGGA_GameplayEffectContainerFunctionLibrary::ApplyEffectContainerSpec(this, Spec); } return TArray(); } void UGGA_GameplayAbility::PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData) { Super::PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData); UAbilitySystemComponent* Comp = ActorInfo->AbilitySystemComponent.Get(); for (const FGGA_GameplayTagCount& TagCount : ActivationOwnedLooseTags) { Comp->AddLooseGameplayTag(TagCount.Tag, TagCount.Count); } } void UGGA_GameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) { if (IsEndAbilityValid(Handle, ActorInfo)) { if (UAbilitySystemComponent* Comp = ActorInfo->AbilitySystemComponent.Get()) { for (const FGGA_GameplayTagCount& TagCount : ActivationOwnedLooseTags) { Comp->RemoveLooseGameplayTag(TagCount.Tag, TagCount.Count); } } } Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); } void UGGA_GameplayAbility::OnActivationFailed_Implementation(const FGameplayTagContainer& FailedReason) const { } bool UGGA_GameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const { if (!ActorInfo || !ActorInfo->AbilitySystemComponent.IsValid()) { return false; } if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags)) { return false; } //@TODO Possibly remove after setting up tag relationships UGGA_AbilitySystemComponent* GASC = CastChecked(ActorInfo->AbilitySystemComponent.Get()); if (GASC->IsActivationGroupBlocked(ActivationGroup)) { if (OptionalRelevantTags) { OptionalRelevantTags->AddTag(GGA_AbilityActivateFailTags::ActivationGroup); } return false; } return true; } void UGGA_GameplayAbility::SetCanBeCanceled(bool bCanBeCanceled) { // The ability can not block canceling if it's replaceable. if (!bCanBeCanceled && (ActivationGroup == EGGA_AbilityActivationGroup::Exclusive_Replaceable)) { UE_LOG(LogGGA_Ability, Error, TEXT("SetCanBeCanceled: Ability [%s] can not block canceling because its activation group is replaceable."), *GetName()); return; } Super::SetCanBeCanceled(bCanBeCanceled); } void UGGA_GameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) { Super::OnGiveAbility(ActorInfo, Spec); K2_OnGiveAbility(); TryActivateAbilityOnSpawn(ActorInfo, Spec); } void UGGA_GameplayAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) { K2_OnRemoveAbility(); Super::OnRemoveAbility(ActorInfo, Spec); } void UGGA_GameplayAbility::OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) { Super::OnAvatarSet(ActorInfo, Spec); K2_OnAvatarSet(); } bool UGGA_GameplayAbility::ShouldActivateAbility(ENetRole Role) const { return K2_ShouldActivateAbility(Role) && Super::ShouldActivateAbility(Role); // Don't violate security policy if we're not the server } bool UGGA_GameplayAbility::K2_ShouldActivateAbility_Implementation(ENetRole Role) const { return true; } void UGGA_GameplayAbility::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) { Super::InputPressed(Handle, ActorInfo, ActivationInfo); K2_OnInputPressed(Handle, *ActorInfo, ActivationInfo); } void UGGA_GameplayAbility::InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) { Super::InputReleased(Handle, ActorInfo, ActivationInfo); K2_OnInputReleased(Handle, *ActorInfo, ActivationInfo); } bool UGGA_GameplayAbility::IsInputPressed() const { FGameplayAbilitySpec* Spec = GetCurrentAbilitySpec(); return Spec && Spec->InputPressed; } bool UGGA_GameplayAbility::BatchRPCTryActivateAbility(FGameplayAbilitySpecHandle InAbilityHandle, bool EndAbilityImmediately) { UGGA_AbilitySystemComponent* ASC = Cast(GetAbilitySystemComponentFromActorInfo()); if (ASC) { return ASC->BatchRPCTryActivateAbility(InAbilityHandle, EndAbilityImmediately); } return false; } void UGGA_GameplayAbility::ExternalEndAbility() { check(CurrentActorInfo); bool bReplicateEndAbility = true; bool bWasCancelled = false; EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicateEndAbility, bWasCancelled); } bool UGGA_GameplayAbility::CheckCost(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, OUT FGameplayTagContainer* OptionalRelevantTags) const { if (!Super::CheckCost(Handle, ActorInfo, OptionalRelevantTags)) { return false; } if (!K2_OnCheckCost(Handle, *ActorInfo)) { return false; } for (TObjectPtr AdditionalCost : AdditionalCosts) { if (AdditionalCost != nullptr) { if (!AdditionalCost->CheckCost(this, Handle, ActorInfo, OptionalRelevantTags)) { return false; } } } return true; } bool UGGA_GameplayAbility::K2_OnCheckCost_Implementation(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo& ActorInfo) const { return true; } void UGGA_GameplayAbility::ApplyCost(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const { Super::ApplyCost(Handle, ActorInfo, ActivationInfo); check(ActorInfo); K2_OnApplyCost(Handle, *ActorInfo, ActivationInfo); // Used to determine if the ability actually hit a target (as some costs are only spent on successful attempts) auto DetermineIfAbilityHitTarget = [&]() { if (ActorInfo->IsNetAuthority()) { if (UGGA_AbilitySystemComponent* ASC = Cast(ActorInfo->AbilitySystemComponent.Get())) { FGameplayAbilityTargetDataHandle TargetData; ASC->GetAbilityTargetData(Handle, ActivationInfo, TargetData); for (int32 TargetDataIdx = 0; TargetDataIdx < TargetData.Data.Num(); ++TargetDataIdx) { if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetData, TargetDataIdx)) { return true; } } } } return false; }; // Pay any additional costs bool bAbilityHitTarget = false; bool bHasDeterminedIfAbilityHitTarget = false; for (TObjectPtr AdditionalCost : AdditionalCosts) { if (AdditionalCost != nullptr) { if (AdditionalCost->ShouldOnlyApplyCostOnHit()) { if (!bHasDeterminedIfAbilityHitTarget) { bAbilityHitTarget = DetermineIfAbilityHitTarget(); bHasDeterminedIfAbilityHitTarget = true; } if (!bAbilityHitTarget) { continue; } } AdditionalCost->ApplyCost(this, Handle, ActorInfo, ActivationInfo); } } } UGameplayEffect* UGGA_GameplayAbility::GetCostGameplayEffect() const { if (TSubclassOf GE = K2_GetCostGameplayEffect()) { if (GE) { return GE->GetDefaultObject(); } return nullptr; } return nullptr; } TSubclassOf UGGA_GameplayAbility::K2_GetCostGameplayEffect_Implementation() const { return CostGameplayEffectClass; } void UGGA_GameplayAbility::K2_OnApplyCost_Implementation(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo& ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const { } void UGGA_GameplayAbility::ApplyAbilityTagsToGameplayEffectSpec(FGameplayEffectSpec& Spec, FGameplayAbilitySpec* AbilitySpec) const { Super::ApplyAbilityTagsToGameplayEffectSpec(Spec, AbilitySpec); } bool UGGA_GameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const { // Define a common lambda to check for blocked tags bool bBlocked = false; auto CheckForBlocked = [&](const FGameplayTagContainer& ContainerA, const FGameplayTagContainer& ContainerB) { // Do we not have any tags in common? Then we're not blocked if (ContainerA.IsEmpty() || ContainerB.IsEmpty() || !ContainerA.HasAny(ContainerB)) { return; } if (OptionalRelevantTags) { // Ensure the global blocking tag is only added once if (!bBlocked) { UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get(); const FGameplayTag& BlockedTag = AbilitySystemGlobals.ActivateFailTagsBlockedTag; OptionalRelevantTags->AddTag(BlockedTag); } // Now append all the blocking tags OptionalRelevantTags->AppendMatchingTags(ContainerA, ContainerB); } bBlocked = true; }; // Define a common lambda to check for missing required tags bool bMissing = false; auto CheckForRequired = [&](const FGameplayTagContainer& TagsToCheck, const FGameplayTagContainer& RequiredTags) { // Do we have no requirements, or have met all requirements? Then nothing's missing if (RequiredTags.IsEmpty() || TagsToCheck.HasAll(RequiredTags)) { return; } if (OptionalRelevantTags) { // Ensure the global missing tag is only added once if (!bMissing) { UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get(); const FGameplayTag& MissingTag = AbilitySystemGlobals.ActivateFailTagsMissingTag; OptionalRelevantTags->AddTag(MissingTag); } FGameplayTagContainer MissingTags = RequiredTags; MissingTags.RemoveTags(TagsToCheck.GetGameplayTagParents()); OptionalRelevantTags->AppendTags(MissingTags); } bMissing = true; }; const UGGA_AbilitySystemComponent* GASC = Cast(&AbilitySystemComponent); static FGameplayTagContainer AllRequiredTags; static FGameplayTagContainer AllBlockedTags; AllRequiredTags = ActivationRequiredTags; AllBlockedTags = ActivationBlockedTags; // Expand our ability tags to add additional required/blocked tags if (GASC) { GASC->GetAdditionalActivationTagRequirements(GetAssetTags(), AllRequiredTags, AllBlockedTags); } // Start by checking all of the blocked tags first (so OptionalRelevantTags will contain blocked tags first) CheckForBlocked(GetAssetTags(), AbilitySystemComponent.GetBlockedAbilityTags()); CheckForBlocked(AbilitySystemComponent.GetOwnedGameplayTags(), AllBlockedTags); // Check to see the required/blocked tags for this ability if (AllBlockedTags.Num() || AllRequiredTags.Num()) { static FGameplayTagContainer AbilitySystemComponentTags; AbilitySystemComponentTags.Reset(); AbilitySystemComponent.GetOwnedGameplayTags(AbilitySystemComponentTags); if (AbilitySystemComponentTags.HasAny(AllBlockedTags)) { if (OptionalRelevantTags) { OptionalRelevantTags->AppendTags(AllBlockedTags); } bBlocked = true; } if (!AbilitySystemComponentTags.HasAll(AllRequiredTags)) { if (OptionalRelevantTags) { OptionalRelevantTags->AppendTags(AllRequiredTags); } bMissing = true; } } if (SourceTags != nullptr) { CheckForBlocked(*SourceTags, SourceBlockedTags); } if (TargetTags != nullptr) { CheckForBlocked(*TargetTags, TargetBlockedTags); } // Now check all required tags CheckForRequired(AbilitySystemComponent.GetOwnedGameplayTags(), AllRequiredTags); if (SourceTags != nullptr) { CheckForRequired(*SourceTags, SourceRequiredTags); } if (TargetTags != nullptr) { CheckForRequired(*TargetTags, TargetRequiredTags); } if (!bBlocked && !bMissing) { // If it's a custom implementation that blocks, we can't specify exactly which tag so just use the generic bBlocked = AbilitySystemComponent.AreAbilityTagsBlocked(GetAssetTags()); if (bBlocked && OptionalRelevantTags) { UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get(); const FGameplayTag& BlockedTag = AbilitySystemGlobals.ActivateFailTagsBlockedTag; OptionalRelevantTags->AddTag(BlockedTag); } } // We succeeded if there were no blocked tags and no missing required tags return !bBlocked && !bMissing; } void UGGA_GameplayAbility::SendTargetDataToServer(const FGameplayAbilityTargetDataHandle& TargetData) { if (IsPredictingClient()) { UAbilitySystemComponent* ASC = CurrentActorInfo->AbilitySystemComponent.Get(); check(ASC); // Create new prediction window for next operation. 为接下来的操作新增一个pk FScopedPredictionWindow(ASC, true); FGameplayTag ApplicationTag; // tell server about it. 告诉服务器设置TargetData,传入技能的uid和激活id,Data和本次操作的id CurrentActorInfo->AbilitySystemComponent->CallServerSetReplicatedTargetData( CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey(), TargetData, ApplicationTag, ASC->ScopedPredictionKey); } } #if WITH_EDITOR EDataValidationResult UGGA_GameplayAbility::IsDataValid(FDataValidationContext& Context) const { if (bReplicateInputDirectly == true) { Context.AddError(FText::FromString(TEXT("bReplicateInputDirectly is not recommended to use according to best practices."))); return EDataValidationResult::Invalid; } if (bServerRespectsRemoteAbilityCancellation == true) { Context.AddError(FText::FromString(TEXT("bServerRespectsRemoteAbilityCancellation is not recommended to use according to best practices."))); return EDataValidationResult::Invalid; } PRAGMA_DISABLE_DEPRECATION_WARNINGS if (InstancingPolicy == EGameplayAbilityInstancingPolicy::NonInstanced) { Context.AddError(FText::FromString(TEXT("NonInstanced ability is deprecated since UE5.5, Use InstancedPerActor as the default to avoid confusing corner cases"))); return EDataValidationResult::Invalid; } PRAGMA_ENABLE_DEPRECATION_WARNINGS // if (ReplicationPolicy == EGameplayAbilityReplicationPolicy::Type::ReplicateYes) // { // Context.AddError(FText::FromString(TEXT("ReplicationPolicy->ReplicateYes is not acceptable, Pelease use other option!"))); // return EDataValidationResult::Invalid; // } // if (!AbilityTriggers.IsEmpty() && NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::Type::ServerInitiated) // { // ValidationErrors.Add(FText::FromString(TEXT("Ability with triggers doesn't work with ServerInitiated Net Execution Policy!"))); // return EDataValidationResult::Invalid; // } // if (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::Type::ServerInitiated) // { // ValidationErrors.Add(FText::FromString(TEXT("NetExecutionPolicy->ServerInitiated is not acceptable, Pelease use other option!"))); // return EDataValidationResult::Invalid; // } if (bHasBlueprintActivateFromEvent && bHasBlueprintActivate && !AbilityTriggers.IsEmpty()) { Context.AddError(FText::FromString(TEXT("ActivateAbilityFromEvent will not run! Please remove ActivateAbility node!"))); return EDataValidationResult::Invalid; } return Super::IsDataValid(Context); } #include "UObject/ObjectSaveContext.h" void UGGA_GameplayAbility::PreSave(FObjectPreSaveContext SaveContext) { Super::PreSave(SaveContext); } #endif