diff --git a/Source/GameBaseFramework/Private/Animation/GBFAnimNotifyState_InputBuffer.cpp b/Source/GameBaseFramework/Private/Animation/GBFAnimNotifyState_InputBuffer.cpp new file mode 100644 index 00000000..45d8363f --- /dev/null +++ b/Source/GameBaseFramework/Private/Animation/GBFAnimNotifyState_InputBuffer.cpp @@ -0,0 +1,77 @@ +#include "Animation/GBFAnimNotifyState_InputBuffer.h" + +#include "GBFLog.h" + +#include + +void UGBFAnimNotifyState_InputBuffer::NotifyBegin( USkeletalMeshComponent * mesh_component, UAnimSequenceBase * animation, float total_duration, const FAnimNotifyEventReference & event_reference ) +{ + Super::NotifyBegin( mesh_component, animation, total_duration, event_reference ); + + if ( auto * ability_input_buffer_component = GetAbilityInputBufferComponent( mesh_component ) ) + { + ability_input_buffer_component->StartMonitoring( InputTagsToCheck, TriggerPriority ); + } +} + +void UGBFAnimNotifyState_InputBuffer::NotifyEnd( USkeletalMeshComponent * mesh_component, UAnimSequenceBase * animation, const FAnimNotifyEventReference & event_reference ) +{ + Super::NotifyEnd( mesh_component, animation, event_reference ); + + if ( auto * ability_input_buffer_component = GetAbilityInputBufferComponent( mesh_component ) ) + { + ability_input_buffer_component->StopMonitoring(); + } +} + +UGBFAbilityInputBufferComponent * UGBFAnimNotifyState_InputBuffer::GetAbilityInputBufferComponent_Implementation( const USkeletalMeshComponent * mesh_component ) const +{ + if ( mesh_component == nullptr ) + { + return nullptr; + } + + const auto * owner = mesh_component->GetOwner(); + if ( owner == nullptr ) + { + UE_LOG( LogGBF, Error, TEXT( "Ability Input Buffer Notify : No Owner found" ) ); + return nullptr; + } + + auto * ability_input_buffer_component = owner->FindComponentByClass< UGBFAbilityInputBufferComponent >(); + + if ( ability_input_buffer_component != nullptr ) + { + return ability_input_buffer_component; + } + + // Check all parent + auto * parent = owner->GetParentActor(); + while ( parent ) + { + ability_input_buffer_component = parent->FindComponentByClass< UGBFAbilityInputBufferComponent >(); + if ( ability_input_buffer_component != nullptr ) + { + return ability_input_buffer_component; + } + parent = parent->GetParentActor(); + } + + // Check all attached actors + TArray< AActor * > actors; + owner->GetAttachedActors( actors, true, true ); + actors.RemoveAll( []( AActor * actor ) { + return !Cast< APawn >( actor ); + } ); + for ( auto & child : actors ) + { + ability_input_buffer_component = child->FindComponentByClass< UGBFAbilityInputBufferComponent >(); + if ( ability_input_buffer_component != nullptr ) + { + return ability_input_buffer_component; + } + } + + UE_LOG( LogGBF, Error, TEXT( "No Ability Input Buffer Component found" ) ); + return nullptr; +} diff --git a/Source/GameBaseFramework/Private/Characters/Components/GBFAbilityInputBufferComponent.cpp b/Source/GameBaseFramework/Private/Characters/Components/GBFAbilityInputBufferComponent.cpp new file mode 100644 index 00000000..205d5a61 --- /dev/null +++ b/Source/GameBaseFramework/Private/Characters/Components/GBFAbilityInputBufferComponent.cpp @@ -0,0 +1,222 @@ +#include "Characters/Components/GBFAbilityInputBufferComponent.h" + +#include "Characters/Components/GBFHeroComponent.h" +#include "Characters/Components/GBFPawnExtensionComponent.h" +#include "GAS/Components/GBFAbilitySystemComponent.h" +#include "Input/GBFInputComponent.h" + +UGBFAbilityInputBufferComponent::UGBFAbilityInputBufferComponent( const FObjectInitializer & object_initializer ) : + Super( object_initializer ) +{ + TriggerPriority = ETriggerPriority::LastTriggeredInput; + this->PrimaryComponentTick.bStartWithTickEnabled = false; + this->PrimaryComponentTick.bCanEverTick = true; +} + +#if !UE_BUILD_SHIPPING +void UGBFAbilityInputBufferComponent::TickComponent( float delta_time, ELevelTick tick_type, FActorComponentTickFunction * this_tick_function ) +{ + Super::TickComponent( delta_time, tick_type, this_tick_function ); + MonitoringTime += delta_time; + if ( !ensureAlwaysMsgf( MonitoringTime <= MaxMonitoringTime, TEXT( "Ability Input Buffer didn't call Stop Monitor %f secs after activation, please call it manually !" ), MaxMonitoringTime ) ) + { + StopMonitoring(); + } +} +#endif + +void UGBFAbilityInputBufferComponent::StartMonitoring( FGameplayTagContainer input_tags_to_check, ETriggerPriority trigger_priority ) +{ + if ( input_tags_to_check.IsEmpty() ) + { + return; + } + + Reset(); + TriggerPriority = trigger_priority; + InputTagsToCheck = input_tags_to_check; + BindActions(); + +#if !UE_BUILD_SHIPPING + SetComponentTickEnabled( true ); +#endif +} + +void UGBFAbilityInputBufferComponent::StopMonitoring() +{ + RemoveBinds(); + TryToTriggerAbility(); + Reset(); +} + +void UGBFAbilityInputBufferComponent::Reset() +{ + TriggeredTags.Reset(); + InputTagsToCheck.Reset(); + BindHandles.Reset(); + MonitoringTime = 0.0f; + +#if !UE_BUILD_SHIPPING + SetComponentTickEnabled( false ); +#endif +} + +void UGBFAbilityInputBufferComponent::BindActions() +{ + if ( InputTagsToCheck.IsEmpty() ) + { + return; + } + + auto * pawn = GetPawn< APawn >(); + if ( pawn == nullptr ) + { + return; + } + + auto * input_component = Cast< UEnhancedInputComponent >( pawn->InputComponent ); + if ( input_component == nullptr ) + { + return; + } + + const auto * hero_component = UGBFHeroComponent::FindHeroComponent( pawn ); + if ( hero_component == nullptr ) + { + return; + } + + for ( auto & input_config : hero_component->GetBoundActionsByInputconfig() ) + { + for ( auto & tag : InputTagsToCheck ) + { + if ( const auto * input_action = input_config.Key->FindAbilityInputActionForTag( tag ) ) + { + BindHandles.Add( input_component->BindAction( input_action, ETriggerEvent::Triggered, this, &ThisClass::AbilityInputTagPressed, tag ).GetHandle() ); + } + } + } +} + +void UGBFAbilityInputBufferComponent::RemoveBinds() +{ + auto * pawn = GetPawn< APawn >(); + if ( pawn == nullptr ) + { + return; + } + + if ( auto * input_component = Cast< UEnhancedInputComponent >( pawn->InputComponent ) ) + { + for ( auto & handle : BindHandles ) + { + input_component->RemoveBindingByHandle( handle ); + } + } +} + +void UGBFAbilityInputBufferComponent::AbilityInputTagPressed( FGameplayTag input_tag ) +{ + TriggeredTags.Add( input_tag ); +} + +bool UGBFAbilityInputBufferComponent::TryToTriggerAbility() +{ + if ( TriggeredTags.IsEmpty() ) + { + return false; + } + + auto * pawn = GetPawn< APawn >(); + if ( pawn == nullptr ) + { + return false; + } + + const auto * pawn_ext_comp = UGBFPawnExtensionComponent::FindPawnExtensionComponent( pawn ); + if ( pawn_ext_comp == nullptr ) + { + return false; + } + + if ( auto * asc = pawn_ext_comp->GetGBFAbilitySystemComponent() ) + { + for ( auto & tagged_ability_tag : InputTagsToCheck ) + { + auto * tagged_ability = asc->FindAbilityByInputTag( tagged_ability_tag ); + asc->CancelAbility( tagged_ability ); + } + + // Try to activate ability in priority order + FGameplayTag tag = TryToGetInputTagWithPriority(); + + while ( tag.IsValid() ) + { + if ( auto * ability = asc->FindAbilityByInputTag( tag ) ) + { + asc->CancelAbility( ability ); + if ( asc->TryActivateAbilityByClass( ability->GetClass() ) ) + { + return true; + } + } + + tag = TryToGetInputTagWithPriority(); + } + } + return false; +} + +FGameplayTag UGBFAbilityInputBufferComponent::TryToGetInputTagWithPriority() +{ + if ( TriggeredTags.IsEmpty() ) + { + return FGameplayTag::EmptyTag; + } + + switch ( TriggerPriority ) + { + case ETriggerPriority::LastTriggeredInput: + return GetLastTriggeredInput(); + + case ETriggerPriority::MostTriggeredInput: + return GetMostTriggeredInput(); + + default: + return FGameplayTag::EmptyTag; + } +} + +FGameplayTag UGBFAbilityInputBufferComponent::GetLastTriggeredInput() +{ + FGameplayTag first_tag = TriggeredTags[ 0 ]; + TriggeredTags.Remove( first_tag ); + return first_tag; +} + +FGameplayTag UGBFAbilityInputBufferComponent::GetMostTriggeredInput() +{ + TSortedMap< int, FGameplayTag > triggered_tag_map; + + // Remove all to get count easily + for ( auto & tag_to_remove : InputTagsToCheck ) + { + int count = TriggeredTags.Remove( tag_to_remove ); + triggered_tag_map.Add( count, tag_to_remove ); + } + + // Get most triggered input + TArray< int > triggered_tag_keys; + triggered_tag_map.GetKeys( triggered_tag_keys ); + int max = triggered_tag_keys[ triggered_tag_map.GetMaxIndex() ]; + + FGameplayTag most_triggered_tag = triggered_tag_map.FindAndRemoveChecked( max ); + + triggered_tag_map.Remove( 0 ); + for ( auto & input : triggered_tag_map ) + { + TriggeredTags.Add( input.Value ); + } + + return most_triggered_tag; +} \ No newline at end of file diff --git a/Source/GameBaseFramework/Private/Characters/Components/GBFHeroComponent.cpp b/Source/GameBaseFramework/Private/Characters/Components/GBFHeroComponent.cpp index d02cb689..3a858610 100644 --- a/Source/GameBaseFramework/Private/Characters/Components/GBFHeroComponent.cpp +++ b/Source/GameBaseFramework/Private/Characters/Components/GBFHeroComponent.cpp @@ -224,6 +224,11 @@ void UGBFHeroComponent::ClearAbilityCameraMode( const FGameplayAbilitySpecHandle } } +const TMap< const UGBFInputConfig *, TArray< uint32 > > & UGBFHeroComponent::GetBoundActionsByInputconfig() const +{ + return BoundActionsByInputConfig; +} + void UGBFHeroComponent::OnRegister() { Super::OnRegister(); diff --git a/Source/GameBaseFramework/Private/GAS/Components/GBFAbilitySystemComponent.cpp b/Source/GameBaseFramework/Private/GAS/Components/GBFAbilitySystemComponent.cpp index 83c8b143..453ebf8c 100644 --- a/Source/GameBaseFramework/Private/GAS/Components/GBFAbilitySystemComponent.cpp +++ b/Source/GameBaseFramework/Private/GAS/Components/GBFAbilitySystemComponent.cpp @@ -379,6 +379,24 @@ FGameplayAbilitySpecHandle UGBFAbilitySystemComponent::FindAbilitySpecHandleForC return FGameplayAbilitySpecHandle(); } +UGameplayAbility * UGBFAbilitySystemComponent::FindAbilityByInputTag( const FGameplayTag input_tag ) const +{ + if ( !input_tag.IsValid() ) + { + return nullptr; + } + + for ( const auto & ability_spec : ActivatableAbilities.Items ) + { + if ( ability_spec.Ability && ability_spec.DynamicAbilityTags.HasTagExact( input_tag ) ) + { + return ability_spec.Ability; + } + } + + return nullptr; +} + void UGBFAbilitySystemComponent::OurCancelAllAbilities() { static const auto GameplayTagContainer = FGameplayTagContainer::CreateFromArray( diff --git a/Source/GameBaseFramework/Public/Animation/GBFAnimNotifyState_InputBuffer.h b/Source/GameBaseFramework/Public/Animation/GBFAnimNotifyState_InputBuffer.h new file mode 100644 index 00000000..3ab32764 --- /dev/null +++ b/Source/GameBaseFramework/Public/Animation/GBFAnimNotifyState_InputBuffer.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Characters/Components/GBFAbilityInputBufferComponent.h" + +#include +#include + +#include "GBFAnimNotifyState_InputBuffer.generated.h" + +class UGBFAbilityInputBufferComponent; + +UCLASS( DisplayName = "Ability Input Buffer Window" ) +class GAMEBASEFRAMEWORK_API UGBFAnimNotifyState_InputBuffer : public UAnimNotifyState +{ + GENERATED_BODY() + +public: + void NotifyBegin( USkeletalMeshComponent * mesh_component, UAnimSequenceBase * animation, float total_duration, const FAnimNotifyEventReference & event_reference ) override; + void NotifyEnd( USkeletalMeshComponent * mesh_component, UAnimSequenceBase * animation, const FAnimNotifyEventReference & event_reference ) override; + +protected: + UFUNCTION( BlueprintNativeEvent ) + UGBFAbilityInputBufferComponent * GetAbilityInputBufferComponent( const USkeletalMeshComponent * mesh_component ) const; + +private: + UPROPERTY( EditAnywhere ) + ETriggerPriority TriggerPriority; + + UPROPERTY( EditAnywhere, Meta = ( Categories = "Input" ) ) + FGameplayTagContainer InputTagsToCheck; +}; diff --git a/Source/GameBaseFramework/Public/Characters/Components/GBFAbilityInputBufferComponent.h b/Source/GameBaseFramework/Public/Characters/Components/GBFAbilityInputBufferComponent.h new file mode 100644 index 00000000..9b3ca45e --- /dev/null +++ b/Source/GameBaseFramework/Public/Characters/Components/GBFAbilityInputBufferComponent.h @@ -0,0 +1,55 @@ +#pragma once + +#include "Characters/Components/GBFPawnComponent.h" + +#include + +#include "GBFAbilityInputBufferComponent.generated.h" + +UENUM( BlueprintType ) +enum class ETriggerPriority : uint8 +{ + LastTriggeredInput = 0 UMETA( DisplayName = "Last Triggered Input" ), + MostTriggeredInput = 1 UMETA( DisplayName = "Most Triggered Input" ) +}; + +UCLASS( Blueprintable ) +class GAMEBASEFRAMEWORK_API UGBFAbilityInputBufferComponent final : public UPawnComponent +{ + GENERATED_BODY() + +public: + UGBFAbilityInputBufferComponent( const FObjectInitializer & object_initializer ); + + UFUNCTION( BlueprintCallable ) + void StartMonitoring( FGameplayTagContainer input_tags_to_check, ETriggerPriority trigger_priority ); + + UFUNCTION( BlueprintCallable ) + void StopMonitoring(); + +#if !UE_BUILD_SHIPPING + void TickComponent( float delta_time, ELevelTick tick_type, FActorComponentTickFunction * this_tick_function ) override; +#endif + +protected: + UPROPERTY( EditDefaultsOnly ) + float MaxMonitoringTime = 5.0f; + + void Reset(); + void BindActions(); + void RemoveBinds(); + void AbilityInputTagPressed( FGameplayTag input_tag ); + bool TryToTriggerAbility(); + + FGameplayTag TryToGetInputTagWithPriority(); + FGameplayTag GetLastTriggeredInput(); + FGameplayTag GetMostTriggeredInput(); + + ETriggerPriority TriggerPriority; + FGameplayTagContainer InputTagsToCheck; + + TArray< FGameplayTag > TriggeredTags; + TArray< uint32 > BindHandles; + + float MonitoringTime = 0.0f; +}; \ No newline at end of file diff --git a/Source/GameBaseFramework/Public/Characters/Components/GBFHeroComponent.h b/Source/GameBaseFramework/Public/Characters/Components/GBFHeroComponent.h index 69c94232..667dafdd 100644 --- a/Source/GameBaseFramework/Public/Characters/Components/GBFHeroComponent.h +++ b/Source/GameBaseFramework/Public/Characters/Components/GBFHeroComponent.h @@ -48,6 +48,8 @@ class GAMEBASEFRAMEWORK_API UGBFHeroComponent : public UGBFPawnComponent /** Clears the camera override if it is set */ void ClearAbilityCameraMode( const FGameplayAbilitySpecHandle & owning_spec_handle ); + const TMap< const UGBFInputConfig *, TArray< uint32 > > & GetBoundActionsByInputconfig() const; + protected: void OnRegister() override; void BindToRequiredOnActorInitStateChanged() override; diff --git a/Source/GameBaseFramework/Public/GAS/Components/GBFAbilitySystemComponent.h b/Source/GameBaseFramework/Public/GAS/Components/GBFAbilitySystemComponent.h index 86b5b319..4f156865 100644 --- a/Source/GameBaseFramework/Public/GAS/Components/GBFAbilitySystemComponent.h +++ b/Source/GameBaseFramework/Public/GAS/Components/GBFAbilitySystemComponent.h @@ -113,6 +113,9 @@ class GAMEBASEFRAMEWORK_API UGBFAbilitySystemComponent : public UAbilitySystemCo UFUNCTION( BlueprintPure ) FGameplayAbilitySpecHandle FindAbilitySpecHandleForClass( const TSubclassOf< UGameplayAbility > & ability_class ); + UFUNCTION( BlueprintPure ) + UGameplayAbility * FindAbilityByInputTag( const FGameplayTag input_tag ) const; + UFUNCTION( BlueprintCallable ) void OurCancelAllAbilities();