diff --git a/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp b/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp index aaab0f1..bd759a0 100644 --- a/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp +++ b/Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp @@ -8,10 +8,16 @@ #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() { @@ -292,10 +298,142 @@ void UOLSAbilitySystemComponent::RemoveGameplayCueLocally( void UOLSAbilitySystemComponent::TryActivateAbilitiesOnSpawn() { ABILITYLIST_SCOPE_LOCK(); - - for (const FGameplayAbilitySpec& abilitySpec : GetActivatableAbilities()) + for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items) { - // if (const UOLS) + if (const UOLSGameplayAbility* abilityCDO = Cast(abilitySpec.Ability)) + { + // @TODO: Implement UOLSGameplayAbility. + // 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(ability)) + { + // @TODO: Implement UOLSGameplayAbility. + // AddAbilityToActivationGroup(olsAbility->GetActivationGroup(), ability); + } +} + +void UOLSAbilitySystemComponent::NotifyAbilityFailed(const FGameplayAbilitySpecHandle handle, + UGameplayAbility* ability, + const FGameplayTagContainer& failureReason) +{ + Super::NotifyAbilityFailed(handle, ability, failureReason); + + if (APawn* avatar = Cast(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(Ability)) + { + // @TODO: Implement UOLSGameplayAbility. + // 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(ability)) + { + // @TODO: Implement UOLSGameplayAbility. + // olsAbility->OnAbilityFailedToActivate(failureReason); } } @@ -327,4 +465,271 @@ void UOLSAbilitySystemComponent::SetReplicatedMontageInfo( } } +void UOLSAbilitySystemComponent::CancelAbilitiesByFunc(TShouldCancelAbilityFunc shouldCancelFunc, + bool shouldReplicateCancelAbility) +{ + ABILITYLIST_SCOPE_LOCK(); + for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items) + { + if (!abilitySpec.IsActive()) + { + continue; + } + + UOLSGameplayAbility* abilityCDO = Cast(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 Instances = abilitySpec.GetAbilityInstances(); + for (UGameplayAbility* AbilityInstance : Instances) + { + UOLSGameplayAbility* abilityInstance = CastChecked(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) +{ + // @TODO: Implement UOLSGameplayAbility + // auto shouldCancelFunc = [this](const UOLSGameplayAbility* ability, FGameplayAbilitySpecHandle handle) + // { + // const ELyraAbilityActivationPolicy ActivationPolicy = ability->GetActivationPolicy(); + // return ((ActivationPolicy == ELyraAbilityActivationPolicy::OnInputTriggered) || (ActivationPolicy == ELyraAbilityActivationPolicy::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 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(abilitySpec->Ability); + + // @TODO: Implement UOLSGameplayAbility. + // if (abilityCDO && abilityCDO->GetActivationPolicy() == ELyraAbilityActivationPolicy::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(abilitySpec->Ability); + + // @TODO: Implement UOLSGameplayAbility. + // if (abilityCDO && abilityCDO->GetActivationPolicy() == ELyraAbilityActivationPolicy::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(); +} + +void UOLSAbilitySystemComponent::AddDynamicTagGameplayEffect(const FGameplayTag& tag) +{ + const TSubclassOf 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 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 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); + } +} + diff --git a/Source/ols/Private/Components/OLSPawnExtensionComponent.cpp b/Source/ols/Private/Components/OLSPawnExtensionComponent.cpp new file mode 100644 index 0000000..f4fb91a --- /dev/null +++ b/Source/ols/Private/Components/OLSPawnExtensionComponent.cpp @@ -0,0 +1,304 @@ +// © 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 "Components/OLSPawnExtensionComponent.h" + +#include "OLSLog.h" +#include "AbilitySystem/OLSAbilitySystemComponent.h" +#include "Components/GameFrameworkComponentManager.h" +#include "DataAssets/OLSPawnDataAsset.h" + +DEFINE_LOG_CATEGORY(LogOLSPawnExtensionComponent); + +UOLSPawnExtensionComponent::UOLSPawnExtensionComponent(const FObjectInitializer& objectInitializer) + : Super(objectInitializer) +{ + PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = false; + + SetIsReplicatedByDefault(true); + + PawnData = nullptr; + AbilitySystemComponent = nullptr; +} + +FName UOLSPawnExtensionComponent::GetFeatureName() const +{ + return NAME_ActorFeatureName; +} + +bool UOLSPawnExtensionComponent::CanChangeInitState( + UGameFrameworkComponentManager* manager, + FGameplayTag currentState, + FGameplayTag desiredState) const +{ + check(manager); + + APawn* pawn = GetPawn(); + + // @TODO: Implement LyraGameplayTags::InitState_Spawned. + if (!currentState.IsValid() /* && desiredState == LyraGameplayTags::InitState_Spawned */) + { + // As long as we are on a valid pawn, we count as spawned + if (pawn) + { + return true; + } + } + + // @TODO: Implement LyraGameplayTags::InitState_Spawned, + // @TODO: Implement LyraGameplayTags::InitState_DataAvailable, + // @TODO: Implement LyraGameplayTags::InitState_DataInitialized, + // @TODO: Implement LyraGameplayTags::InitState_GameplayReady + // if (currentState == LyraGameplayTags::InitState_Spawned && desiredState == LyraGameplayTags::InitState_DataAvailable) + // { + // // Pawn data is required. + // if (!PawnData) + // { + // return false; + // } + // + // const bool bHasAuthority = pawn->HasAuthority(); + // const bool bIsLocallyControlled = pawn->IsLocallyControlled(); + // + // if (bHasAuthority || bIsLocallyControlled) + // { + // // Check for being possessed by a controller. + // if (!GetController()) + // { + // return false; + // } + // } + // + // return true; + // } + // else if (currentState == LyraGameplayTags::InitState_DataAvailable && desiredState == LyraGameplayTags::InitState_DataInitialized) + // { + // // Transition to initialize if all features have their data available + // return manager->HaveAllFeaturesReachedInitState(pawn, LyraGameplayTags::InitState_DataAvailable); + // } + // else if (currentState == LyraGameplayTags::InitState_DataInitialized && desiredState == LyraGameplayTags::InitState_GameplayReady) + // { + // return true; + // } + + return false; +} + +void UOLSPawnExtensionComponent::HandleChangeInitState( + UGameFrameworkComponentManager* manager, + FGameplayTag currentState, + FGameplayTag desiredState) +{ + // @TODO: Implement LyraGameplayTags::InitState_Spawned. + // if (desiredState == LyraGameplayTags::InitState_DataInitialized) + // { + // // This is currently all handled by other components listening to this state change + // } +} + +void UOLSPawnExtensionComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params) +{ + // If another feature is now in DataAvailable, see if we should transition to DataInitialized + if (Params.FeatureName != NAME_ActorFeatureName) + { + // @TODO: Implement LyraGameplayTags::InitState_DataAvailable. + // if (Params.FeatureState == LyraGameplayTags::InitState_DataAvailable) + // { + // CheckDefaultInitialization(); + // } + } +} + +void UOLSPawnExtensionComponent::CheckDefaultInitialization() +{ + // Before checking our progress, try progressing any other features we might depend on + CheckDefaultInitializationForImplementers(); + + // @TODO: Implement LyraGameplayTags::InitState_Spawned, + // @TODO: Implement LyraGameplayTags::InitState_DataAvailable, + // @TODO: Implement LyraGameplayTags::InitState_DataInitialized, + // @TODO: Implement LyraGameplayTags::InitState_GameplayReady + // static const TArray StateChain = { + // LyraGameplayTags::InitState_Spawned, LyraGameplayTags::InitState_DataAvailable, + // LyraGameplayTags::InitState_DataInitialized, LyraGameplayTags::InitState_GameplayReady + // }; + + // This will try to progress from spawned (which is only set in BeginPlay) through the data initialization stages until it gets to gameplay ready + // ContinueInitStateChain(StateChain); +} + +UOLSPawnExtensionComponent* UOLSPawnExtensionComponent::FindPawnExtensionComponent(const AActor* actor) +{ + return (actor ? actor->FindComponentByClass() : nullptr); +} + +UOLSAbilitySystemComponent* UOLSPawnExtensionComponent::GetOLSAbilitySystemComponent() const +{ + return AbilitySystemComponent; +} + +void UOLSPawnExtensionComponent::SetPawnData(const UOLSPawnDataAsset* pawnData) +{ + check(pawnData); + + APawn* pawn = GetPawnChecked(); + + if (pawn->GetLocalRole() != ROLE_Authority) + { + return; + } + + if (PawnData) + { + OLS_LOG(LogOLSPawnExtensionComponent, Error, + TEXT("Trying to set PawnData [%s] on pawn [%s] that already has valid PawnData [%s]."), + GET_UOBJECT_NAME(pawnData), GET_UOBJECT_NAME(pawn), GET_UOBJECT_NAME(PawnData)); + return; + } + + PawnData = pawnData; + + pawn->ForceNetUpdate(); + + CheckDefaultInitialization(); +} + +void UOLSPawnExtensionComponent::InitializeAbilitySystem(UOLSAbilitySystemComponent* asc, AActor* ownerActor) +{ + check(asc); + check(ownerActor); + + if (AbilitySystemComponent == asc) + { + // The ability system component hasn't changed. + return; + } + + if (AbilitySystemComponent) + { + // Clean up the old ability system component. + UninitializeAbilitySystem(); + } + + APawn* pawn = GetPawnChecked(); + AActor* existingAvatar = asc->GetAvatarActor(); + + OLS_LOG(LogOLSPawnExtensionComponent, Verbose, TEXT("Setting up ASC [%s] on pawn [%s] owner [%s], existing [%s] "), + GET_UOBJECT_NAME(asc), GET_UOBJECT_NAME(pawn), GET_UOBJECT_NAME(ownerActor), + GET_UOBJECT_NAME(existingAvatar)); + + if ((existingAvatar != nullptr) && (existingAvatar != pawn)) + { + OLS_LOG(LogOLSPawnExtensionComponent, Log, TEXT("Existing avatar (authority=%d)"), existingAvatar->HasAuthority() ? 1 : 0); + + // There is already a pawn acting as the ASC's avatar, so we need to kick it out + // This can happen on clients if they're lagged: their new pawn is spawned + possessed before the dead one is removed + ensure(!existingAvatar->HasAuthority()); + + if (UOLSPawnExtensionComponent* OtherExtensionComponent = FindPawnExtensionComponent(existingAvatar)) + { + OtherExtensionComponent->UninitializeAbilitySystem(); + } + } + + AbilitySystemComponent = asc; + AbilitySystemComponent->InitAbilityActorInfo(ownerActor, pawn); + + if (ensure(PawnData)) + { + asc->SetTagRelationshipMapping(PawnData->TagRelationshipMapping); + } + + OnAbilitySystemInitialized.Broadcast(); +} + +void UOLSPawnExtensionComponent::UninitializeAbilitySystem() +{ + if (!AbilitySystemComponent) + { + return; + } + + // Uninitialize the ASC if we're still the avatar actor (otherwise another pawn already did it when they became the avatar actor) + if (AbilitySystemComponent->GetAvatarActor() == GetOwner()) + { + FGameplayTagContainer abilityTypesToIgnore; + + // @TOD:; Implement LyraGameplayTags::Ability_Behavior_SurvivesDeath; + // abilityTypesToIgnore.AddTag(LyraGameplayTags::Ability_Behavior_SurvivesDeath); + + AbilitySystemComponent->CancelAbilities(nullptr, &abilityTypesToIgnore); + AbilitySystemComponent->ClearAbilityInput(); + AbilitySystemComponent->RemoveAllGameplayCues(); + + if (AbilitySystemComponent->GetOwnerActor() != nullptr) + { + AbilitySystemComponent->SetAvatarActor(nullptr); + } + else + { + // If the ASC doesn't have a valid owner, we need to clear *all* actor info, not just the avatar pairing + AbilitySystemComponent->ClearActorInfo(); + } + + OnAbilitySystemUninitialized.Broadcast(); + } + + AbilitySystemComponent = nullptr; +} + +void UOLSPawnExtensionComponent::HandleControllerChanged() +{ + if (AbilitySystemComponent && (AbilitySystemComponent->GetAvatarActor() == GetPawnChecked())) + { + ensure(AbilitySystemComponent->AbilityActorInfo->OwnerActor == AbilitySystemComponent->GetOwnerActor()); + if (AbilitySystemComponent->GetOwnerActor() == nullptr) + { + UninitializeAbilitySystem(); + } + else + { + AbilitySystemComponent->RefreshAbilityActorInfo(); + } + } + + CheckDefaultInitialization(); +} + +void UOLSPawnExtensionComponent::HandlePlayerStateReplicated() +{ + CheckDefaultInitialization(); +} + +void UOLSPawnExtensionComponent::SetupPlayerInputComponent() +{ + CheckDefaultInitialization(); +} + +void UOLSPawnExtensionComponent::OnAbilitySystemInitialized_RegisterAndCall( + FSimpleMulticastDelegate::FDelegate delegate) +{ + if (!OnAbilitySystemInitialized.IsBoundToObject(delegate.GetUObject())) + { + OnAbilitySystemInitialized.Add(delegate); + } + + if (AbilitySystemComponent) + { + delegate.Execute(); + } +} + +void UOLSPawnExtensionComponent::OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate delegate) +{ + if (!OnAbilitySystemUninitialized.IsBoundToObject(delegate.GetUObject())) + { + OnAbilitySystemUninitialized.Add(delegate); + } +} + +void UOLSPawnExtensionComponent::OnRep_PawnData() +{ + CheckDefaultInitialization(); +} diff --git a/Source/ols/Public/AbilitySystem/OLSAbilitySystemComponent.h b/Source/ols/Public/AbilitySystem/OLSAbilitySystemComponent.h index befffea..36a8848 100644 --- a/Source/ols/Public/AbilitySystem/OLSAbilitySystemComponent.h +++ b/Source/ols/Public/AbilitySystem/OLSAbilitySystemComponent.h @@ -4,10 +4,13 @@ #include "CoreMinimal.h" #include "AbilitySystemComponent.h" +#include "NativeGameplayTags.h" #include "OLSAbilitySystemComponent.generated.h" DECLARE_LOG_CATEGORY_EXTERN(LogOLSAbilitySystemComponent, Verbose, All); +OLS_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Gameplay_AbilityInputBlocked); + /** * CVAR to control the "Play Montage" flow. * Example: OLS.EnableDefaultPlayMontage true @@ -44,7 +47,6 @@ public: float startTimeSeconds) override; // -- End Ability System Component implementation - public: /** @@ -151,14 +153,80 @@ protected: * Typically used for gameplay abilities that need to be initialized or activated as soon as the actor is created. */ void TryActivateAbilitiesOnSpawn(); + + virtual void AbilitySpecInputPressed(FGameplayAbilitySpec& spec) override; + virtual void AbilitySpecInputReleased(FGameplayAbilitySpec& spec) override; + + virtual void NotifyAbilityActivated(const FGameplayAbilitySpecHandle handle, UGameplayAbility* ability) override; + virtual void NotifyAbilityFailed(const FGameplayAbilitySpecHandle handle, UGameplayAbility* ability, const FGameplayTagContainer& failureReason) override; + virtual void NotifyAbilityEnded(FGameplayAbilitySpecHandle handle, UGameplayAbility* Ability, bool bWasCancelled) override; + virtual void ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& abilityTags, UGameplayAbility* requestingAbility, bool shouldEnableBlockTags, const FGameplayTagContainer& blockTags, bool shouldExecuteCancelTags, const FGameplayTagContainer& cancelTags) override; + virtual void HandleChangeAbilityCanBeCanceled(const FGameplayTagContainer& abilityTags, UGameplayAbility* requestingAbility, bool canBeCanceled) override; + + /** Notify client that an ability failed to activate */ + UFUNCTION(Client, Unreliable) + void ClientNotifyAbilityFailed(const UGameplayAbility* ability, const FGameplayTagContainer& failureReason); + + void HandleAbilityFailed(const UGameplayAbility* ability, const FGameplayTagContainer& failureReason); /** * Conveniently separates the code that sets the animation to replicate, so it can be further modified. */ virtual void SetReplicatedMontageInfo(FGameplayAbilityRepAnimMontage& mutableRepAnimMontageInfo, UAnimMontage* newMontageToPlay, const FName& startSectionName); +public: + + typedef TFunctionRef TShouldCancelAbilityFunc; + void CancelAbilitiesByFunc(TShouldCancelAbilityFunc shouldCancelFunc, bool shouldReplicateCancelAbility); + + void CancelInputActivatedAbilities(bool shouldReplicateCancelAbility); + + void AbilityInputTagPressed(const FGameplayTag& inputTag); + void AbilityInputTagReleased(const FGameplayTag& inputTag); + + void ProcessAbilityInput(float deltaTime, bool shouldGamePaused); + void ClearAbilityInput(); + + // @TODO: Implement UOLSGameplayAbility. + // bool IsActivationGroupBlocked(ELyraAbilityActivationGroup Group) const; + // void AddAbilityToActivationGroup(ELyraAbilityActivationGroup Group, ULyraGameplayAbility* LyraAbility); + // void RemoveAbilityFromActivationGroup(ELyraAbilityActivationGroup Group, ULyraGameplayAbility* LyraAbility); + // void CancelActivationGroupAbilities(ELyraAbilityActivationGroup Group, ULyraGameplayAbility* IgnoreLyraAbility, bool bReplicateCancelAbility); + + // Uses a gameplay effect to add the specified dynamic granted tag. + void AddDynamicTagGameplayEffect(const FGameplayTag& tag); + + // Removes all active instances of the gameplay effect that was used to add the specified dynamic granted tag. + void RemoveDynamicTagGameplayEffect(const FGameplayTag& Tag); + + /** Gets the ability target data associated with the given ability handle and activation info */ + void GetAbilityTargetData(const FGameplayAbilitySpecHandle abilityHandle, FGameplayAbilityActivationInfo activationInfo, FGameplayAbilityTargetDataHandle& outTargetDataHandle); + + /** Sets the current tag relationship mapping, if null it will clear it out */ + void SetTagRelationshipMapping(class UOLSAbilityTagRelationshipMappingDataAsset* newMapping); + + /** Looks at ability tags and gathers additional required and blocking tags */ + void GetAdditionalActivationTagRequirements(const FGameplayTagContainer& abilityTags, FGameplayTagContainer& outActivationRequired, FGameplayTagContainer& outActivationBlocked) const; + + + private: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "OLS Ability System", DisplayName = "Enable Ability Batch RPCs", meta = (AllowPrivateAccess = true)) uint8 bShouldEnableBatchRPC : 1 = true; + +protected: + + // If set, this table is used to look up tag relationships for activate and cancel + UPROPERTY() + TObjectPtr TagRelationshipMapping = nullptr; + + // Handles to abilities that had their input pressed this frame. + TArray InputPressedSpecHandles; + + // Handles to abilities that had their input released this frame. + TArray InputReleasedSpecHandles; + + // Handles to abilities that have their input held. + TArray InputHeldSpecHandles; }; diff --git a/Source/ols/Public/Components/OLSPawnExtensionComponent.h b/Source/ols/Public/Components/OLSPawnExtensionComponent.h new file mode 100644 index 0000000..cfeca69 --- /dev/null +++ b/Source/ols/Public/Components/OLSPawnExtensionComponent.h @@ -0,0 +1,99 @@ +// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/GameFrameworkInitStateInterface.h" +#include "Components/PawnComponent.h" +#include "OLSPawnExtensionComponent.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogOLSPawnExtensionComponent, Verbose, All); + +namespace EEndPlayReason { enum Type : int; } + +/** + * Component that adds functionality to all Pawn classes so it can be used for characters/vehicles/etc. + * This coordinates the initialization of other components. + */ +UCLASS() +class OLS_API UOLSPawnExtensionComponent : public UPawnComponent, public IGameFrameworkInitStateInterface +{ + GENERATED_BODY() + +public: + + // Sets default values for this component's properties + UOLSPawnExtensionComponent(const FObjectInitializer& objectInitializer); + + /** The name of this overall feature, this one depends on the other named component features */ + static const FName NAME_ActorFeatureName; + + //~ Begin IGameFrameworkInitStateInterface interface + virtual FName GetFeatureName() const override; + virtual bool CanChangeInitState(UGameFrameworkComponentManager* manager, FGameplayTag currentState, FGameplayTag desiredState) const override; + virtual void HandleChangeInitState(UGameFrameworkComponentManager* manager, FGameplayTag currentState, FGameplayTag desiredState) override; + virtual void OnActorInitStateChanged(const FActorInitStateChangedParams& Params) override; + virtual void CheckDefaultInitialization() override; + //~ End IGameFrameworkInitStateInterface interface + + /** Returns the pawn extension component if one exists on the specified actor. */ + UFUNCTION(BlueprintPure, Category = "OLS|Pawn") + static class UOLSPawnExtensionComponent* FindPawnExtensionComponent(const AActor* actor); + + /** Gets the current ability system component, which may be owned by a different actor */ + UFUNCTION(BlueprintPure, Category = "OLS|Pawn") + class UOLSAbilitySystemComponent* GetOLSAbilitySystemComponent() const ; + +public: + + /** Gets the pawn data, which is used to specify pawn properties in data */ + template + const T* GetPawnData() const { return Cast(PawnData); } + + /** Sets the current pawn data */ + void SetPawnData(const class UOLSPawnDataAsset* pawnData); + + /** Should be called by the owning pawn to become the avatar of the ability system. */ + void InitializeAbilitySystem(class UOLSAbilitySystemComponent* asc, AActor* ownerActor); + + /** Should be called by the owning pawn to remove itself as the avatar of the ability system. */ + void UninitializeAbilitySystem(); + + /** Should be called by the owning pawn when the pawn's controller changes. */ + void HandleControllerChanged(); + + /** Should be called by the owning pawn when the player state has been replicated. */ + void HandlePlayerStateReplicated(); + + /** Should be called by the owning pawn when the input component is setup. */ + void SetupPlayerInputComponent(); + + /** Register with the OnAbilitySystemInitialized delegate and broadcast if our pawn has been registered with the ability system component */ + void OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate delegate); + + /** Register with the OnAbilitySystemUninitialized delegate fired when our pawn is removed as the ability system's avatar actor */ + void OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate delegate); + +protected: + + UFUNCTION() + void OnRep_PawnData(); + +protected: + + /** Delegate fired when our pawn becomes the ability system's avatar actor */ + FSimpleMulticastDelegate OnAbilitySystemInitialized; + + /** Delegate fired when our pawn is removed as the ability system's avatar actor */ + FSimpleMulticastDelegate OnAbilitySystemUninitialized; + +protected: + + /** Pawn data used to create the pawn. Specified from a spawn function or on a placed instance. */ + UPROPERTY(EditInstanceOnly, ReplicatedUsing = OnRep_PawnData, Category = "Lyra|Pawn") + TObjectPtr PawnData = nullptr; + + /** Pointer to the ability system component that is cached for convenience. */ + UPROPERTY(Transient) + TObjectPtr AbilitySystemComponent = nullptr; +};