OLS/Source/ols/Private/AbilitySystem/Abilities/OLSGameplayAbility.cpp
LongLy 57b53b9c0c Implemented OLSCameraMode.
Addressed @TODOs related to custom logs and OLSCameraMode
2025-01-20 14:08:07 -07:00

576 lines
18 KiB
C++

// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
#include "AbilitySystem/Abilities/OLSGameplayAbility.h"
#include "OLSLog.h"
#include "AbilitySystem/OLSAbilitySimpleFailureMessage.h"
#include "AbilitySystem/OLSAbilitySystemComponent.h"
#include "AbilitySystem/Abilities/OLSAbilityCost.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemGlobals.h"
#include "AbilitySystem/OLSGameplayEffectContext.h"
#include "AbilitySystem/Interfaces/OLSAbilitySourceInterface.h"
#include "Camera/OLSCameraMode.h"
#include "Components/OLSHeroComponent.h"
#include "GameFramework/GameplayMessageSubsystem.h"
#include "Physics/OLSPhysicalMaterialWithTags.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameplayAbility)
DEFINE_LOG_CATEGORY(LogOLSGameplayAbility);
#define ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(FunctionName, ReturnValue) \
{ \
if (!ensure(IsInstantiated())) \
{ \
OLS_LOG(LogOLSGameplayAbility, Error, TEXT("%s: " #FunctionName " cannot be called on a non-instanced ability. Check the instancing policy."), *GetPathName()); \
return ReturnValue; \
} \
}
UE_DEFINE_GAMEPLAY_TAG(TAG_ABILITY_SIMPLE_FAILURE_MESSAGE, "Ability.UserFacingSimpleActivateFail.Message");
UE_DEFINE_GAMEPLAY_TAG(TAG_ABILITY_PLAY_MONTAGE_FAILURE_MESSAGE, "Ability.PlayMontageOnActivateFail.Message");
UOLSGameplayAbility::UOLSGameplayAbility(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
{
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer;
ActivationPolicy = EOLSAbilityActivationPolicy::OnInputTriggered;
ActivationGroup = EOLSAbilityActivationGroup::Independent;
bShouldLogCancellation = false;
ActiveCameraMode = nullptr;
}
bool UOLSGameplayAbility::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
UOLSAbilitySystemComponent* asc = CastChecked<UOLSAbilitySystemComponent>(actorInfo->AbilitySystemComponent.Get());
if (asc->IsActivationGroupBlocked(ActivationGroup))
{
if (optionalRelevantTags)
{
// @TODO: Implement LyraGameplayTags::Ability_ActivateFail_ActivationGroup.
// optionalRelevantTags->AddTag(LyraGameplayTags::Ability_ActivateFail_ActivationGroup);
}
return false;
}
return true;
}
void UOLSGameplayAbility::SetCanBeCanceled(bool canBeCanceled)
{
// The ability can not block canceling if it's replaceable.
if (!canBeCanceled && (ActivationGroup == EOLSAbilityActivationGroup::Exclusive_Replaceable))
{
OLS_LOG(LogOLSGameplayAbility, Error,
TEXT(
"Ability [%s] can not block canceling because its activation group is replaceable."
), *GetName());
return;
}
Super::SetCanBeCanceled(canBeCanceled);
}
void UOLSGameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec)
{
Super::OnGiveAbility(actorInfo, spec);
K2_OnAbilityAdded();
TryActivateAbilityOnSpawn(actorInfo, spec);
}
void UOLSGameplayAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec)
{
K2_OnAbilityRemoved();
Super::OnRemoveAbility(actorInfo, spec);
}
void UOLSGameplayAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
}
void UOLSGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility, bool bWasCancelled)
{
ClearCameraMode();
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
bool UOLSGameplayAbility::CheckCost(const FGameplayAbilitySpecHandle handle,
const FGameplayAbilityActorInfo* actorInfo,
FGameplayTagContainer* optionalRelevantTags) const
{
if (!Super::CheckCost(handle, actorInfo, optionalRelevantTags) || !actorInfo)
{
return false;
}
// Verify we can afford any additional costs
for (const TObjectPtr<UOLSAbilityCost>& additionalCost : AdditionalCosts)
{
if (additionalCost)
{
if (!additionalCost->CheckCost(this, handle, actorInfo, /*inout*/ optionalRelevantTags))
{
return false;
}
}
}
return true;
}
void UOLSGameplayAbility::ApplyCost(const FGameplayAbilitySpecHandle handle,
const FGameplayAbilityActorInfo* actorInfo,
const FGameplayAbilityActivationInfo activationInfo) const
{
check(actorInfo);
// 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 (UOLSAbilitySystemComponent* ASC = Cast<UOLSAbilitySystemComponent>(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 hasAbilityHitTarget = false;
bool hasDeterminedIfAbilityHitTarget = false;
for (const TObjectPtr<UOLSAbilityCost>& additionalCost : AdditionalCosts)
{
if (additionalCost)
{
if (additionalCost->ShouldOnlyApplyCostOnHit())
{
if (!hasDeterminedIfAbilityHitTarget)
{
hasAbilityHitTarget = determineIfAbilityHitTarget();
hasDeterminedIfAbilityHitTarget = true;
}
if (!hasAbilityHitTarget)
{
continue;
}
}
additionalCost->ApplyCost(this, handle, actorInfo, activationInfo);
}
}
}
FGameplayEffectContextHandle UOLSGameplayAbility::MakeEffectContext(const FGameplayAbilitySpecHandle handle,
const FGameplayAbilityActorInfo* actorInfo) const
{
FGameplayEffectContextHandle contextHandle = Super::MakeEffectContext(handle, actorInfo);
FOLSGameplayEffectContext* effectContext = FOLSGameplayEffectContext::ExtractEffectContext(contextHandle);
check(effectContext);
check(actorInfo);
AActor* effectCauser = nullptr;
const IOLSAbilitySourceInterface* abilitySource = nullptr;
float sourceLevel = 0.0f;
GetAbilitySource(handle, actorInfo, /*out*/ sourceLevel, /*out*/ abilitySource, /*out*/ effectCauser);
UObject* sourceObject = GetSourceObject(handle, actorInfo);
AActor* instigator = actorInfo ? actorInfo->OwnerActor.Get() : nullptr;
effectContext->SetAbilitySource(abilitySource, sourceLevel);
effectContext->AddInstigator(instigator, effectCauser);
effectContext->AddSourceObject(sourceObject);
return contextHandle;
}
void UOLSGameplayAbility::ApplyAbilityTagsToGameplayEffectSpec(FGameplayEffectSpec& spec,
FGameplayAbilitySpec* abilitySpec) const
{
Super::ApplyAbilityTagsToGameplayEffectSpec(spec, abilitySpec);
if (const FHitResult* hitResult = spec.GetContext().GetHitResult())
{
if (const UOLSPhysicalMaterialWithTags* physMatWithTags = Cast<const UOLSPhysicalMaterialWithTags>(hitResult->PhysMaterial.Get()))
{
spec.CapturedTargetTags.GetSpecTags().AppendTags(physMatWithTags->Tags);
}
}
}
bool UOLSGameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& abilitySystemComponent,
const FGameplayTagContainer* sourceTags,
const FGameplayTagContainer* targetTags,
FGameplayTagContainer* optionalRelevantTags) const
{
// Specialized version to handle death exclusion and AbilityTags expansion via ASC
bool isBlocked = false;
bool isMissing = false;
UAbilitySystemGlobals& abilitySystemGlobals = UAbilitySystemGlobals::Get();
const FGameplayTag& blockedTag = abilitySystemGlobals.ActivateFailTagsBlockedTag;
const FGameplayTag& missingTag = abilitySystemGlobals.ActivateFailTagsMissingTag;
// Check if any of this ability's tags are currently blocked
if (abilitySystemComponent.AreAbilityTagsBlocked(GetAssetTags()))
{
isBlocked = true;
}
const UOLSAbilitySystemComponent* asc = Cast<UOLSAbilitySystemComponent>(&abilitySystemComponent);
static FGameplayTagContainer allRequiredTags;
static FGameplayTagContainer allBlockedTags;
allRequiredTags = ActivationRequiredTags;
allBlockedTags = ActivationBlockedTags;
// Expand our ability tags to add additional required/blocked tags
if (asc)
{
asc->GetAdditionalActivationTagRequirements(GetAssetTags(), allRequiredTags, 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))
{
// @TODO: Implement LyraGameplayTags::Status_Death.
// @TODO: Implement LyraGameplayTags::Ability_ActivateFail_IsDead.
// if (optionalRelevantTags && abilitySystemComponentTags.HasTag(LyraGameplayTags::Status_Death))
// {
// // If player is dead and was rejected due to blocking tags, give that feedback
// optionalRelevantTags->AddTag(LyraGameplayTags::Ability_ActivateFail_IsDead);
// }
isBlocked = true;
}
if (!abilitySystemComponentTags.HasAll(allRequiredTags))
{
isMissing = true;
}
}
if (sourceTags != nullptr)
{
if (SourceBlockedTags.Num() || SourceRequiredTags.Num())
{
if (sourceTags->HasAny(SourceBlockedTags))
{
isBlocked = true;
}
if (!sourceTags->HasAll(SourceRequiredTags))
{
isMissing = true;
}
}
}
if (targetTags != nullptr)
{
if (TargetBlockedTags.Num() || TargetRequiredTags.Num())
{
if (targetTags->HasAny(TargetBlockedTags))
{
isBlocked = true;
}
if (!targetTags->HasAll(TargetRequiredTags))
{
isMissing = true;
}
}
}
if (isBlocked)
{
if (optionalRelevantTags && blockedTag.IsValid())
{
optionalRelevantTags->AddTag(blockedTag);
}
return false;
}
if (isMissing)
{
if (optionalRelevantTags && missingTag.IsValid())
{
optionalRelevantTags->AddTag(missingTag);
}
return false;
}
return true;
}
UOLSAbilitySystemComponent* UOLSGameplayAbility::GetOLSAbilitySystemComponentFromActorInfo() const
{
return (CurrentActorInfo ? Cast<UOLSAbilitySystemComponent>(CurrentActorInfo->AbilitySystemComponent.Get()) : nullptr);
}
AController* UOLSGameplayAbility::GetControllerFromActorInfo() const
{
if (CurrentActorInfo)
{
if (AController* controller = CurrentActorInfo->PlayerController.Get())
{
return controller;
}
// Look for a player controller or pawn in the owner chain.
AActor* testActor = CurrentActorInfo->OwnerActor.Get();
while (testActor)
{
if (AController* controller = Cast<AController>(testActor))
{
return controller;
}
if (APawn* pawn = Cast<APawn>(testActor))
{
return pawn->GetController();
}
testActor = testActor->GetOwner();
}
}
return nullptr;
}
UOLSHeroComponent* UOLSGameplayAbility::GetHeroComponentFromActorInfo() const
{
return (CurrentActorInfo ? UOLSHeroComponent::FindHeroComponent(CurrentActorInfo->AvatarActor.Get()) : nullptr);
}
bool UOLSGameplayAbility::CanChangeActivationGroup(EOLSAbilityActivationGroup newGroup) const
{
if (!IsInstantiated() || !IsActive())
{
return false;
}
if (ActivationGroup == newGroup)
{
return true;
}
UOLSAbilitySystemComponent* asc = GetOLSAbilitySystemComponentFromActorInfo();
check(asc);
if ((ActivationGroup != EOLSAbilityActivationGroup::Exclusive_Blocking) && asc->IsActivationGroupBlocked(newGroup))
{
// This ability can't change groups if it's blocked (unless it is the one doing the blocking).
return false;
}
if ((newGroup == EOLSAbilityActivationGroup::Exclusive_Replaceable) && !CanBeCanceled())
{
// This ability can't become replaceable if it can't be canceled.
return false;
}
return true;
}
bool UOLSGameplayAbility::ChangeActivationGroup(EOLSAbilityActivationGroup newGroup)
{
ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(ChangeActivationGroup, false);
if (!CanChangeActivationGroup(newGroup))
{
return false;
}
if (ActivationGroup != newGroup)
{
UOLSAbilitySystemComponent* asc = GetOLSAbilitySystemComponentFromActorInfo();
check(asc);
asc->RemoveAbilityFromActivationGroup(ActivationGroup, this);
asc->AddAbilityToActivationGroup(newGroup, this);
ActivationGroup = newGroup;
}
return true;
}
void UOLSGameplayAbility::SetCameraMode(TSubclassOf<UOLSCameraMode> cameraMode)
{
ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(SetCameraMode, );
if (UOLSHeroComponent* heroComponent = GetHeroComponentFromActorInfo())
{
heroComponent->SetAbilityCameraMode(cameraMode, CurrentSpecHandle);
ActiveCameraMode = cameraMode;
}
}
void UOLSGameplayAbility::ClearCameraMode()
{
ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(ClearCameraMode, );
if (ActiveCameraMode)
{
if (UOLSHeroComponent* heroComponent = GetHeroComponentFromActorInfo())
{
heroComponent->ClearAbilityCameraMode(CurrentSpecHandle);
}
ActiveCameraMode = nullptr;
}
}
EOLSAbilityActivationPolicy UOLSGameplayAbility::GetActivationPolicy() const
{
return ActivationPolicy;
}
EOLSAbilityActivationGroup UOLSGameplayAbility::GetActivationGroup() const
{
return ActivationGroup;
}
void UOLSGameplayAbility::TryActivateAbilityOnSpawn(const FGameplayAbilityActorInfo* actorInfo,
const FGameplayAbilitySpec& spec) const
{
// Try to activate if activation policy is on spawn.
if (actorInfo && !spec.IsActive() && (ActivationPolicy == EOLSAbilityActivationPolicy::OnSpawn))
{
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 isLocalExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalPredicted) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalOnly);
const bool iServerExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerOnly) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerInitiated);
const bool shouldClientActivate = actorInfo->IsLocallyControlled() && isLocalExecution;
const bool shouldServerActivate = actorInfo->IsNetAuthority() && iServerExecution;
if (shouldClientActivate || shouldServerActivate)
{
asc->TryActivateAbility(spec.Handle);
}
}
}
}
void UOLSGameplayAbility::OnAbilityFailedToActivate(const FGameplayTagContainer& failedReason) const
{
NativeOnAbilityFailedToActivate(failedReason);
K2_OnAbilityFailedToActivate(failedReason);
}
void UOLSGameplayAbility::OnPawnAvatarSet()
{
K2_OnPawnAvatarSet();
}
void UOLSGameplayAbility::GetAbilitySource(FGameplayAbilitySpecHandle handle,
const FGameplayAbilityActorInfo* actorInfo,
float& outSourceLevel,
const IOLSAbilitySourceInterface*& outAbilitySource,
AActor*& outEffectCauser) const
{
outSourceLevel = 0.0f;
outAbilitySource = nullptr;
outEffectCauser = nullptr;
outEffectCauser = actorInfo->AvatarActor.Get();
// If we were added by something that's an ability info source, use it
UObject* sourceObject = GetSourceObject(handle, actorInfo);
outAbilitySource = Cast<IOLSAbilitySourceInterface>(sourceObject);
}
void UOLSGameplayAbility::NativeOnAbilityFailedToActivate(const FGameplayTagContainer& failedReason) const
{
bool simpleFailureFound = false;
for (FGameplayTag reason : failedReason)
{
if (!simpleFailureFound)
{
if (const FText* pUserFacingMessage = FailureTagToUserFacingMessages.Find(reason))
{
FOLSAbilitySimpleFailureMessage message;
message.PlayerController = GetActorInfo().PlayerController.Get();
message.FailureTags = failedReason;
message.UserFacingReason = *pUserFacingMessage;
UGameplayMessageSubsystem& messageSystem = UGameplayMessageSubsystem::Get(GetWorld());
messageSystem.BroadcastMessage(TAG_ABILITY_SIMPLE_FAILURE_MESSAGE, message);
simpleFailureFound = true;
}
}
if (UAnimMontage* montage = FailureTagToAnimMontage.FindRef(reason))
{
FOLSAbilityMontageFailureMessage message;
message.PlayerController = GetActorInfo().PlayerController.Get();
message.AvatarActor = GetActorInfo().AvatarActor.Get();
message.FailureTags = failedReason;
message.FailureMontage = montage;
UGameplayMessageSubsystem& messageSystem = UGameplayMessageSubsystem::Get(GetWorld());
messageSystem.BroadcastMessage(TAG_ABILITY_PLAY_MONTAGE_FAILURE_MESSAGE, message);
}
}
}