331 lines
11 KiB
C++
331 lines
11 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 "AnimInstances/OLSBaseLayerAnimInstance.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogOLSAbilitySystemComponent);
|
|
|
|
// 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 : GetActivatableAbilities())
|
|
{
|
|
// if (const UOLS)
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|