// © 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(avatarActor) && (avatarActor != actorInfo->AvatarActor); Super::InitAbilityActorInfo(ownerActor, avatarActor); // Apply the new defaults obtained from the owner's interface. if (hasAvatarChanged) { if (const TObjectPtr globalAbilitySystem = UWorld::GetSubsystem(GetWorld())) { globalAbilitySystem->RegisterASC(this); } if (const TObjectPtr animInstance = Cast(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 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 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 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()) { 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 cueManager = UAbilitySystemGlobals::Get().GetGameplayCueManager(); cueManager->HandleGameplayCue( GetOwner(), gameplayCueTag, EGameplayCueEvent::Type::Executed, gameplayCueParameters); } void UOLSAbilitySystemComponent::AddGameplayCueLocally( const FGameplayTag gameplayCueTag, const FGameplayCueParameters& gameplayCueParameters) const { const TObjectPtr 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 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 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; } }