815 lines
28 KiB
C++
815 lines
28 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/OLSAbilitySystemComponent.h"
|
|
|
|
#include "AbilitySystemGlobals.h"
|
|
#include "GameplayCueManager.h"
|
|
#include "OLSLog.h"
|
|
#include "AbilitySystem/OLSBatchGameplayAbilityInterface.h"
|
|
#include "AbilitySystem/OLSGlobaAbilitySubsystem.h"
|
|
#include "AbilitySystem/Abilities/OLSGameplayAbility.h"
|
|
#include "AnimInstances/OLSBaseLayerAnimInstance.h"
|
|
#include "DataAssets/OLSAbilityTagRelationshipMappingDataAsset.h"
|
|
#include "DataAssets/OLSGameDataAsset.h"
|
|
#include "Systems/OLSAssetManager.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogOLSAbilitySystemComponent);
|
|
|
|
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_AbilityInputBlocked, "Gameplay.AbilityInputBlocked");
|
|
|
|
// Sets default values for this component's properties
|
|
UOLSAbilitySystemComponent::UOLSAbilitySystemComponent()
|
|
{
|
|
static constexpr bool isReplicated = true;
|
|
SetIsReplicatedByDefault(isReplicated);
|
|
|
|
bShouldEnableBatchRPC = true;
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::InitAbilityActorInfo(AActor* ownerActor, AActor* avatarActor)
|
|
{
|
|
FGameplayAbilityActorInfo* actorInfo = AbilityActorInfo.Get();
|
|
check(actorInfo);
|
|
check(ownerActor);
|
|
|
|
// Guard condition to ensure we should clear/init for this new Avatar Actor.
|
|
const bool hasAvatarChanged = avatarActor && Cast<APawn>(avatarActor) && (avatarActor != actorInfo->AvatarActor);
|
|
|
|
Super::InitAbilityActorInfo(ownerActor, avatarActor);
|
|
|
|
// Apply the new defaults obtained from the owner's interface.
|
|
if (hasAvatarChanged)
|
|
{
|
|
if (const TObjectPtr<UOLSGlobaAbilitySubsystem> globalAbilitySystem = UWorld::GetSubsystem<UOLSGlobaAbilitySubsystem>(GetWorld()))
|
|
{
|
|
globalAbilitySystem->RegisterASC(this);
|
|
}
|
|
|
|
if (const TObjectPtr<UOLSBaseLayerAnimInstance> animInstance = Cast<UOLSBaseLayerAnimInstance>(GetAnimInstanceFromActorInfo()))
|
|
{
|
|
animInstance->InitializeWithAbilitySystem(this);
|
|
}
|
|
|
|
TryActivateAbilitiesOnSpawn();
|
|
}
|
|
}
|
|
|
|
bool UOLSAbilitySystemComponent::ShouldDoServerAbilityRPCBatch() const
|
|
{
|
|
return bShouldEnableBatchRPC;
|
|
}
|
|
|
|
float UOLSAbilitySystemComponent::PlayMontage(
|
|
UGameplayAbility* animatingAbility,
|
|
FGameplayAbilityActivationInfo activationInfo,
|
|
UAnimMontage* montage,
|
|
float playRate,
|
|
FName startSectionName,
|
|
float startTimeSeconds)
|
|
{
|
|
if (GEnableDefaultPlayMontage)
|
|
{
|
|
// Always useful to still allow the default flow, if there are some meaningful changes in the core system
|
|
// that were not yet reflect in this custom implementation. Can be enabled with CVar "GEnableDefaultPlayMontage".
|
|
//
|
|
return Super::PlayMontage(animatingAbility, activationInfo, montage, playRate, startSectionName, startTimeSeconds);
|
|
}
|
|
|
|
float duration = -1.f;
|
|
|
|
// This method was re-written just to ensure that the Animation Instance is retrieved from the Actor Info
|
|
// by default, but also, other scenarios can be supported. Biggest example being an IK Runtime Retarget.
|
|
//
|
|
// This virtual "GetAnimInstanceFromActorInfo" provides some flexibility on how the Anim Instance is
|
|
// retrieved. It can be extended in projects that should support IK Runtime Retargets and also traditional
|
|
// Anim Instances set in the Actor Info.
|
|
//
|
|
const TObjectPtr<UAnimInstance> animInstance = GetAnimInstanceFromActorInfo();
|
|
if (animInstance && montage)
|
|
{
|
|
duration = animInstance->Montage_Play(
|
|
montage,
|
|
playRate,
|
|
EMontagePlayReturnType::MontageLength,
|
|
startTimeSeconds);
|
|
if (duration > 0.f)
|
|
{
|
|
if (montage->HasRootMotion() && animInstance->GetOwningActor())
|
|
{
|
|
UE_LOG(LogRootMotion, Log, TEXT("UAbilitySystemComponent::PlayMontage %s, Role: %s")
|
|
, *GetNameSafe(montage)
|
|
, *UEnum::GetValueAsString(TEXT("Engine.ENetRole"), animInstance->GetOwningActor()->GetLocalRole())
|
|
);
|
|
}
|
|
|
|
LocalAnimMontageInfo.AnimMontage = montage;
|
|
LocalAnimMontageInfo.AnimatingAbility = animatingAbility;
|
|
LocalAnimMontageInfo.PlayInstanceId = (LocalAnimMontageInfo.PlayInstanceId < UINT8_MAX ? LocalAnimMontageInfo.PlayInstanceId + 1 : 0);
|
|
|
|
if (animatingAbility)
|
|
{
|
|
animatingAbility->SetCurrentMontage(montage);
|
|
}
|
|
|
|
// Start at a given Section.
|
|
if (startSectionName != NAME_None)
|
|
{
|
|
animInstance->Montage_JumpToSection(startSectionName, montage);
|
|
}
|
|
|
|
// Replicate for non-owners and for replay recordings
|
|
// The data we set from GetRepAnimMontageInfo_Mutable() is used both by the server to replicate to clients and by clients to record replays.
|
|
// We need to set this data for recording clients because there exists network configurations where an abilities montage data will not replicate to some clients (for example: if the client is an autonomous proxy.)
|
|
if (ShouldRecordMontageReplication())
|
|
{
|
|
FGameplayAbilityRepAnimMontage& mutableRepAnimMontageInfo = GetRepAnimMontageInfo_Mutable();
|
|
SetReplicatedMontageInfo(mutableRepAnimMontageInfo, montage, startSectionName);
|
|
|
|
// Update parameters that change during Montage lifetime.
|
|
AnimMontage_UpdateReplicatedData();
|
|
}
|
|
|
|
// Replicate to non-owners
|
|
if (IsOwnerActorAuthoritative())
|
|
{
|
|
// Force net update on our avatar actor.
|
|
if (AbilityActorInfo->AvatarActor != nullptr)
|
|
{
|
|
AbilityActorInfo->AvatarActor->ForceNetUpdate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If this prediction key is rejected, we need to end the preview
|
|
FPredictionKey predictionKey = GetPredictionKeyForNewAction();
|
|
if (predictionKey.IsValidKey())
|
|
{
|
|
predictionKey.NewRejectedDelegate().BindUObject(this, &ThisClass::OnPredictiveMontageRejected, montage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
UAnimInstance* UOLSAbilitySystemComponent::GetAnimInstanceFromActorInfo() const
|
|
{
|
|
if (!AbilityActorInfo.IsValid())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const FGameplayAbilityActorInfo* actorInfo = AbilityActorInfo.Get();
|
|
if (actorInfo->AnimInstance.IsValid() && actorInfo->AnimInstance->IsValidLowLevelFast())
|
|
{
|
|
// Return the one that was deliberately set in the Actor Info.
|
|
return actorInfo->AnimInstance.Get();
|
|
}
|
|
|
|
// Otherwise, let the getter method try to figure out the animation instance.
|
|
return actorInfo->GetAnimInstance();
|
|
}
|
|
|
|
FActiveGameplayEffectHandle UOLSAbilitySystemComponent::ApplyGameplayEffectClassToSelf(
|
|
TSubclassOf<UGameplayEffect> effectClass,
|
|
float level)
|
|
{
|
|
FActiveGameplayEffectHandle handle;
|
|
|
|
if (IsValid(effectClass))
|
|
{
|
|
FGameplayEffectContextHandle contextHandle = MakeEffectContext();
|
|
contextHandle.AddSourceObject(GetOwner());
|
|
|
|
const FGameplayEffectSpecHandle specHandle = MakeOutgoingSpec(effectClass, level, contextHandle);
|
|
if (specHandle.IsValid())
|
|
{
|
|
handle = ApplyGameplayEffectSpecToSelf(*specHandle.Data.Get());
|
|
|
|
OLS_LOG(LogOLSAbilitySystemComponent, Verbose, TEXT("[%s] Effect '%s' granted at level %f."),
|
|
GET_UOBJECT_NAME(GetAvatarActor()), GET_UOBJECT_NAME(effectClass), level);
|
|
}
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
FGameplayAbilitySpecHandle UOLSAbilitySystemComponent::GiveAbilityFromClass(
|
|
const TSubclassOf<UGameplayAbility> abilityClass,
|
|
int32 level,
|
|
int32 input)
|
|
{
|
|
FGameplayAbilitySpecHandle handle;
|
|
|
|
if (IsValid(abilityClass))
|
|
{
|
|
const FGameplayAbilitySpec newAbilitySpec(FGameplayAbilitySpec(abilityClass, level, input, GetOwner()));
|
|
handle = GiveAbility(newAbilitySpec);
|
|
|
|
OLS_LOG(LogOLSAbilitySystemComponent, Log, TEXT("[%s] Ability '%s' %s at level %d."),
|
|
GET_UOBJECT_NAME(GetAvatarActor()), GET_UOBJECT_NAME(abilityClass),
|
|
handle.IsValid() ? TEXT("successfully granted") : TEXT("failed to be granted"), level);
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
bool UOLSAbilitySystemComponent::TryBatchActivateAbility(
|
|
FGameplayAbilitySpecHandle abilityHandle,
|
|
bool shouldEndAbilityImmediately)
|
|
{
|
|
bool isAbilityActivated = false;
|
|
if (abilityHandle.IsValid())
|
|
{
|
|
OLS_LOG(LogAbilitySystemComponent, Warning, TEXT("Ability handle is invalid!"));
|
|
return isAbilityActivated;
|
|
}
|
|
|
|
FScopedServerAbilityRPCBatcher batch(this, abilityHandle);
|
|
isAbilityActivated = TryActivateAbility(abilityHandle, true);
|
|
|
|
if (!shouldEndAbilityImmediately)
|
|
{
|
|
const FGameplayAbilitySpec* abilitySpec = FindAbilitySpecFromHandle(abilityHandle);
|
|
if (abilitySpec != nullptr)
|
|
{
|
|
UGameplayAbility* ability = abilitySpec->GetPrimaryInstance();
|
|
if (IsValid(ability) && ability->Implements<UOLSBatchGameplayAbilityInterface>())
|
|
{
|
|
IOLSBatchGameplayAbilityInterface::Execute_EndAbilityFromBatch(ability);
|
|
}
|
|
else
|
|
{
|
|
OLS_LOG(LogAbilitySystemComponent, Error,
|
|
TEXT("%s does not implement Batch Gameplay Ability Interface"), GET_UOBJECT_NAME(ability));
|
|
}
|
|
}
|
|
}
|
|
|
|
return isAbilityActivated;
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::CancelAbilitiesByTags(
|
|
FGameplayTagContainer abilityTags,
|
|
FGameplayTagContainer cancelFilterTags)
|
|
{
|
|
CancelAbilities(&abilityTags, &cancelFilterTags);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::ExecuteGameplayCueLocal(
|
|
const FGameplayTag gameplayCueTag,
|
|
const FGameplayCueParameters& gameplayCueParameters) const
|
|
{
|
|
const TObjectPtr<UGameplayCueManager> cueManager = UAbilitySystemGlobals::Get().GetGameplayCueManager();
|
|
cueManager->HandleGameplayCue(
|
|
GetOwner(),
|
|
gameplayCueTag,
|
|
EGameplayCueEvent::Type::Executed,
|
|
gameplayCueParameters);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::AddGameplayCueLocally(
|
|
const FGameplayTag gameplayCueTag,
|
|
const FGameplayCueParameters& gameplayCueParameters) const
|
|
{
|
|
const TObjectPtr<UGameplayCueManager> cueManager = UAbilitySystemGlobals::Get().GetGameplayCueManager();
|
|
cueManager->HandleGameplayCue(
|
|
GetOwner(),
|
|
gameplayCueTag,
|
|
EGameplayCueEvent::Type::OnActive,
|
|
gameplayCueParameters);
|
|
cueManager->HandleGameplayCue(
|
|
GetOwner(),
|
|
gameplayCueTag,
|
|
EGameplayCueEvent::Type::WhileActive,
|
|
gameplayCueParameters);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::RemoveGameplayCueLocally(
|
|
const FGameplayTag gameplayCueTag,
|
|
const FGameplayCueParameters& gameplayCueParameters) const
|
|
{
|
|
const TObjectPtr<UGameplayCueManager> cueManager = UAbilitySystemGlobals::Get().GetGameplayCueManager();
|
|
cueManager->HandleGameplayCue(GetOwner(), gameplayCueTag, EGameplayCueEvent::Type::Removed, gameplayCueParameters);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::TryActivateAbilitiesOnSpawn()
|
|
{
|
|
ABILITYLIST_SCOPE_LOCK();
|
|
for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items)
|
|
{
|
|
if (const UOLSGameplayAbility* abilityCDO = Cast<UOLSGameplayAbility>(abilitySpec.Ability))
|
|
{
|
|
abilityCDO->TryActivateAbilityOnSpawn(AbilityActorInfo.Get(), abilitySpec);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::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())
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
const UGameplayAbility* instance = spec.GetPrimaryInstance();
|
|
FPredictionKey originalPredictionKey = instance
|
|
? instance->GetCurrentActivationInfo().GetActivationPredictionKey()
|
|
: spec.ActivationInfo.GetActivationPredictionKey();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// 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, originalPredictionKey);
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::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())
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
const UGameplayAbility* instance = spec.GetPrimaryInstance();
|
|
FPredictionKey originalPredictionKey = instance
|
|
? instance->GetCurrentActivationInfo().GetActivationPredictionKey()
|
|
: spec.ActivationInfo.GetActivationPredictionKey();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// 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, originalPredictionKey);
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::NotifyAbilityActivated(const FGameplayAbilitySpecHandle handle,
|
|
UGameplayAbility* ability)
|
|
{
|
|
Super::NotifyAbilityActivated(handle, ability);
|
|
|
|
if (UOLSGameplayAbility* olsAbility = Cast<UOLSGameplayAbility>(ability))
|
|
{
|
|
AddAbilityToActivationGroup(olsAbility->GetActivationGroup(), olsAbility);
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::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 UOLSAbilitySystemComponent::NotifyAbilityEnded(FGameplayAbilitySpecHandle handle,
|
|
UGameplayAbility* ability,
|
|
bool wasCancelled)
|
|
{
|
|
Super::NotifyAbilityEnded(handle, ability, wasCancelled);
|
|
|
|
if (UOLSGameplayAbility* olsAbility = Cast<UOLSGameplayAbility>(ability))
|
|
{
|
|
RemoveAbilityFromActivationGroup(olsAbility->GetActivationGroup(), olsAbility);
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& abilityTags,
|
|
UGameplayAbility* requestingAbility,
|
|
bool shouldEnableBlockTags,
|
|
const FGameplayTagContainer& blockTags,
|
|
bool shouldExecuteCancelTags,
|
|
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, shouldEnableBlockTags, modifiedBlockTags,
|
|
shouldExecuteCancelTags, modifiedCancelTags);
|
|
|
|
//@TODO: Apply any special logic like blocking input or movement
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::HandleChangeAbilityCanBeCanceled(const FGameplayTagContainer& abilityTags,
|
|
UGameplayAbility* requestingAbility,
|
|
bool canBeCanceled)
|
|
{
|
|
Super::HandleChangeAbilityCanBeCanceled(abilityTags, requestingAbility, canBeCanceled);
|
|
|
|
//@TODO: Apply any special logic like blocking input or movement
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::ClientNotifyAbilityFailed_Implementation(const UGameplayAbility* ability,
|
|
const FGameplayTagContainer& failureReason)
|
|
{
|
|
HandleAbilityFailed(ability, failureReason);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::HandleAbilityFailed(const UGameplayAbility* ability,
|
|
const FGameplayTagContainer& failureReason)
|
|
{
|
|
if (const UOLSGameplayAbility* olsAbility = Cast<const UOLSGameplayAbility>(ability))
|
|
{
|
|
olsAbility->OnAbilityFailedToActivate(failureReason);
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::SetReplicatedMontageInfo(
|
|
FGameplayAbilityRepAnimMontage& mutableRepAnimMontageInfo,
|
|
UAnimMontage* newMontageToPlay,
|
|
const FName& startSectionName)
|
|
{
|
|
const uint8 playInstanceId = mutableRepAnimMontageInfo.PlayInstanceId < UINT8_MAX ? mutableRepAnimMontageInfo.PlayInstanceId + 1 : 0;
|
|
const uint8 sectionIdToPlay = newMontageToPlay->GetSectionIndex(startSectionName) + 1;
|
|
|
|
TObjectPtr<UAnimSequenceBase> animation = newMontageToPlay;
|
|
if (newMontageToPlay->IsDynamicMontage())
|
|
{
|
|
animation = newMontageToPlay->GetFirstAnimReference();
|
|
|
|
check(!newMontageToPlay->SlotAnimTracks.IsEmpty());
|
|
mutableRepAnimMontageInfo.SlotName = newMontageToPlay->SlotAnimTracks[0].SlotName;
|
|
mutableRepAnimMontageInfo.BlendOutTime = newMontageToPlay->GetDefaultBlendInTime();
|
|
}
|
|
|
|
mutableRepAnimMontageInfo.Animation = animation;
|
|
mutableRepAnimMontageInfo.PlayInstanceId = playInstanceId;
|
|
|
|
mutableRepAnimMontageInfo.SectionIdToPlay = 0;
|
|
if (mutableRepAnimMontageInfo.Animation && startSectionName != NAME_None)
|
|
{
|
|
mutableRepAnimMontageInfo.SectionIdToPlay = sectionIdToPlay;
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::CancelAbilitiesByFunc(TShouldCancelAbilityFunc shouldCancelFunc,
|
|
bool shouldReplicateCancelAbility)
|
|
{
|
|
ABILITYLIST_SCOPE_LOCK();
|
|
for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items)
|
|
{
|
|
if (!abilitySpec.IsActive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UOLSGameplayAbility* abilityCDO = Cast<UOLSGameplayAbility>(abilitySpec.Ability);
|
|
if (!abilityCDO)
|
|
{
|
|
OLS_LOG(LogOLSAbilitySystemComponent, Error,
|
|
TEXT("CancelAbilitiesByFunc: Non-LyraGameplayAbility %s was Granted to ASC. Skipping."),
|
|
GET_UOBJECT_NAME(abilitySpec.Ability));
|
|
continue;
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
ensureMsgf(abilitySpec.Ability->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced, TEXT("CancelAbilitiesByFunc: All Abilities should be Instanced (NonInstanced is being deprecated due to usability issues)."));
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// Cancel all the spawned instances.
|
|
TArray<UGameplayAbility*> Instances = abilitySpec.GetAbilityInstances();
|
|
for (UGameplayAbility* AbilityInstance : Instances)
|
|
{
|
|
UOLSGameplayAbility* abilityInstance = CastChecked<UOLSGameplayAbility>(AbilityInstance);
|
|
|
|
if (shouldCancelFunc(abilityInstance, abilitySpec.Handle))
|
|
{
|
|
if (abilityInstance->CanBeCanceled())
|
|
{
|
|
abilityInstance->CancelAbility(abilitySpec.Handle, AbilityActorInfo.Get(), abilityInstance->GetCurrentActivationInfo(), shouldReplicateCancelAbility);
|
|
}
|
|
else
|
|
{
|
|
OLS_LOG(LogOLSAbilitySystemComponent, Error,
|
|
TEXT("CancelAbilitiesByFunc: Can't cancel ability [%s] because CanBeCanceled is false."),
|
|
GET_UOBJECT_NAME(abilityInstance));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::CancelInputActivatedAbilities(bool shouldReplicateCancelAbility)
|
|
{
|
|
auto shouldCancelFunc = [this](const UOLSGameplayAbility* ability, FGameplayAbilitySpecHandle handle)
|
|
{
|
|
const EOLSAbilityActivationPolicy activationPolicy = ability->GetActivationPolicy();
|
|
return ((activationPolicy == EOLSAbilityActivationPolicy::OnInputTriggered) || (activationPolicy == EOLSAbilityActivationPolicy::WhileInputActive));
|
|
};
|
|
|
|
CancelAbilitiesByFunc(shouldCancelFunc, shouldReplicateCancelAbility);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::AbilityInputTagPressed(const FGameplayTag& inputTag)
|
|
{
|
|
if (inputTag.IsValid())
|
|
{
|
|
for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items)
|
|
{
|
|
if (abilitySpec.Ability && (abilitySpec.GetDynamicSpecSourceTags().HasTagExact(inputTag)))
|
|
{
|
|
InputPressedSpecHandles.AddUnique(abilitySpec.Handle);
|
|
InputHeldSpecHandles.AddUnique(abilitySpec.Handle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::AbilityInputTagReleased(const FGameplayTag& inputTag)
|
|
{
|
|
if (inputTag.IsValid())
|
|
{
|
|
for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items)
|
|
{
|
|
if (abilitySpec.Ability && (abilitySpec.GetDynamicSpecSourceTags().HasTagExact(inputTag)))
|
|
{
|
|
InputReleasedSpecHandles.AddUnique(abilitySpec.Handle);
|
|
InputHeldSpecHandles.Remove(abilitySpec.Handle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::ProcessAbilityInput(float deltaTime, bool shouldGamePaused)
|
|
{
|
|
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 UOLSGameplayAbility* abilityCDO = Cast<UOLSGameplayAbility>(abilitySpec->Ability);
|
|
|
|
if (abilityCDO && abilityCDO->GetActivationPolicy() == EOLSAbilityActivationPolicy::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 UOLSGameplayAbility* abilityCDO = Cast<UOLSGameplayAbility>(abilitySpec->Ability);
|
|
|
|
if (abilityCDO && abilityCDO->GetActivationPolicy() == EOLSAbilityActivationPolicy::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 UOLSAbilitySystemComponent::ClearAbilityInput()
|
|
{
|
|
InputPressedSpecHandles.Reset();
|
|
InputReleasedSpecHandles.Reset();
|
|
InputHeldSpecHandles.Reset();
|
|
}
|
|
|
|
bool UOLSAbilitySystemComponent::IsActivationGroupBlocked(EOLSAbilityActivationGroup Group) const
|
|
{
|
|
bool isBlocked = false;
|
|
|
|
switch (Group)
|
|
{
|
|
case EOLSAbilityActivationGroup::Independent:
|
|
// Independent abilities are never blocked.
|
|
isBlocked = false;
|
|
break;
|
|
|
|
case EOLSAbilityActivationGroup::Exclusive_Replaceable:
|
|
case EOLSAbilityActivationGroup::Exclusive_Blocking:
|
|
// Exclusive abilities can activate if nothing is blocking.
|
|
isBlocked = (ActivationGroupCounts[(uint8)EOLSAbilityActivationGroup::Exclusive_Blocking] > 0);
|
|
break;
|
|
|
|
default:
|
|
checkf(false, TEXT("IsActivationGroupBlocked: Invalid ActivationGroup [%d]\n"), (uint8)Group);
|
|
break;
|
|
}
|
|
|
|
return isBlocked;
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::AddAbilityToActivationGroup(EOLSAbilityActivationGroup group,
|
|
UOLSGameplayAbility* ability)
|
|
{
|
|
check(ability);
|
|
check(ActivationGroupCounts[(uint8)group] < INT32_MAX);
|
|
|
|
ActivationGroupCounts[(uint8)group]++;
|
|
|
|
constexpr bool shouldReplicateCancelAbility = false;
|
|
|
|
switch (group)
|
|
{
|
|
case EOLSAbilityActivationGroup::Independent:
|
|
// Independent abilities do not cancel any other abilities.
|
|
break;
|
|
|
|
case EOLSAbilityActivationGroup::Exclusive_Replaceable:
|
|
case EOLSAbilityActivationGroup::Exclusive_Blocking:
|
|
CancelActivationGroupAbilities(EOLSAbilityActivationGroup::Exclusive_Replaceable, ability, shouldReplicateCancelAbility);
|
|
break;
|
|
|
|
default:
|
|
checkf(false, TEXT("AddAbilityToActivationGroup: Invalid ActivationGroup [%d]\n"), (uint8)group);
|
|
break;
|
|
}
|
|
|
|
const int32 exclusiveCount = ActivationGroupCounts[static_cast<uint8>(
|
|
EOLSAbilityActivationGroup::Exclusive_Replaceable)] + ActivationGroupCounts[static_cast<uint8>(
|
|
EOLSAbilityActivationGroup::Exclusive_Blocking)];
|
|
if (!ensure(exclusiveCount <= 1))
|
|
{
|
|
OLS_LOG(LogOLSAbilitySystemComponent, Error,
|
|
TEXT("Multiple exclusive abilities are running."));
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::RemoveAbilityFromActivationGroup(EOLSAbilityActivationGroup group,
|
|
UOLSGameplayAbility* ability)
|
|
{
|
|
check(ability);
|
|
check(ActivationGroupCounts[(uint8)group] > 0);
|
|
|
|
ActivationGroupCounts[(uint8)group]--;
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::CancelActivationGroupAbilities(EOLSAbilityActivationGroup group,
|
|
UOLSGameplayAbility* ignoreAbility,
|
|
bool shouldReplicateCancelAbility)
|
|
{
|
|
auto shouldCancelFunc = [this, group, ignoreAbility](const UOLSGameplayAbility* ability,
|
|
FGameplayAbilitySpecHandle handle)
|
|
{
|
|
return ((ability->GetActivationGroup() == group) && (ability != ignoreAbility));
|
|
};
|
|
|
|
CancelAbilitiesByFunc(shouldCancelFunc, shouldReplicateCancelAbility);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::AddDynamicTagGameplayEffect(const FGameplayTag& tag)
|
|
{
|
|
const TSubclassOf<UGameplayEffect> dynamicTagGE = UOLSAssetManager::GetSubclass(UOLSGameDataAsset::Get().DynamicTagGameplayEffect);
|
|
if (!dynamicTagGE)
|
|
{
|
|
OLS_LOG(LogOLSAbilitySystemComponent, Warning,
|
|
TEXT("AddDynamicTagGameplayEffect: Unable to find DynamicTagGameplayEffect [%s]."),
|
|
*UOLSGameDataAsset::Get().DynamicTagGameplayEffect.GetAssetName());
|
|
return;
|
|
}
|
|
|
|
const FGameplayEffectSpecHandle specHandle = MakeOutgoingSpec(dynamicTagGE, 1.0f, MakeEffectContext());
|
|
FGameplayEffectSpec* spec = specHandle.Data.Get();
|
|
|
|
if (!spec)
|
|
{
|
|
OLS_LOG(LogOLSAbilitySystemComponent, Warning,
|
|
TEXT("AddDynamicTagGameplayEffect: Unable to make outgoing spec for [%s]."),
|
|
GET_UOBJECT_NAME(dynamicTagGE));
|
|
return;
|
|
}
|
|
|
|
spec->DynamicGrantedTags.AddTag(tag);
|
|
|
|
ApplyGameplayEffectSpecToSelf(*spec);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::RemoveDynamicTagGameplayEffect(const FGameplayTag& Tag)
|
|
{
|
|
const TSubclassOf<UGameplayEffect> dynamicTagGE = UOLSAssetManager::GetSubclass(UOLSGameDataAsset::Get().DynamicTagGameplayEffect);
|
|
if (!dynamicTagGE)
|
|
{
|
|
OLS_LOG(LogOLSAbilitySystemComponent, Warning,
|
|
TEXT("RemoveDynamicTagGameplayEffect: Unable to find gameplay effect [%s]."),
|
|
*UOLSGameDataAsset::Get().DynamicTagGameplayEffect.GetAssetName());
|
|
return;
|
|
}
|
|
|
|
FGameplayEffectQuery query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(FGameplayTagContainer(Tag));
|
|
query.EffectDefinition = dynamicTagGE;
|
|
|
|
RemoveActiveEffects(query);
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::GetAbilityTargetData(const FGameplayAbilitySpecHandle abilityHandle,
|
|
FGameplayAbilityActivationInfo activationInfo,
|
|
FGameplayAbilityTargetDataHandle& outTargetDataHandle)
|
|
{
|
|
TSharedPtr<FAbilityReplicatedDataCache> replicatedData = AbilityTargetDataMap.Find(
|
|
FGameplayAbilitySpecHandleAndPredictionKey(abilityHandle, activationInfo.GetActivationPredictionKey()));
|
|
if (replicatedData.IsValid())
|
|
{
|
|
outTargetDataHandle = replicatedData->TargetData;
|
|
}
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::SetTagRelationshipMapping(UOLSAbilityTagRelationshipMappingDataAsset* newMapping)
|
|
{
|
|
TagRelationshipMapping = newMapping;
|
|
}
|
|
|
|
void UOLSAbilitySystemComponent::GetAdditionalActivationTagRequirements(const FGameplayTagContainer& abilityTags,
|
|
FGameplayTagContainer& outActivationRequired,
|
|
FGameplayTagContainer& outActivationBlocked)
|
|
const
|
|
{
|
|
if (TagRelationshipMapping)
|
|
{
|
|
TagRelationshipMapping->GetRequiredAndBlockedActivationTags(abilityTags, &outActivationRequired,
|
|
&outActivationBlocked);
|
|
}
|
|
}
|
|
|
|
|