OLS/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp
2025-01-16 12:05:19 -07:00

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;
}
}