Skip to content

Use new class hierarchy UGBFInteractionAllowCondition to allow interactions #399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 19, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "Characters/Components/GBFHeroComponent.h"
#include "Input/GBFInputComponent.h"
#include "Interaction/GBFInteractableComponent.h"
#include "Interaction/GBFInteractionAllowCondition.h"
#include "Interaction/GBFInteractionEventCustomization.h"
#include "Interaction/GBFInteractionOption.h"
#include "Interaction/GBFInteractionStatics.h"
Expand Down Expand Up @@ -122,7 +123,14 @@ void UGBFGameplayAbility_Interact::UpdateInteractableOptions( const TArray< UGBF
TActorToComponentMap previous_active_targets;
fill_actor_to_component_map( previous_active_targets );

ResetUnusedInteractions( target_infos );
// Clear every interaction option and rebuild from scratch because some options can become invalid from frame to frame (tags added to the player, etc...)
for ( auto & [ actor, context ] : InteractableTargetContexts )
{
context.Reset();
}

InteractableTargetContexts.Reset();

RegisterInteractions( target_infos );

TActorToComponentMap new_active_targets;
Expand Down Expand Up @@ -278,49 +286,6 @@ void UGBFGameplayAbility_Interact::GatherTargetInfos( TArray< InteractableTarget
} );
}

void UGBFGameplayAbility_Interact::ResetUnusedInteractions( const TArray< InteractableTargetInfos > & target_infos )
{
TArray< TWeakObjectPtr< AActor >, TInlineAllocator< 8 > > actors_to_unregister;
InteractableTargetContexts.GetKeys( actors_to_unregister );

for ( auto index = 0; index < target_infos.Num(); ++index )
{
const auto & infos = target_infos[ index ];

auto remove_actor = false;

if ( auto * context = InteractableTargetContexts.Find( infos.Actor.Get() ) )
{
if ( context->InteractionsId == infos.InteractableComponent->GetInteractableOptions().GetInteractionsId() )
{
remove_actor = true;
}
}
else
{
remove_actor = true;
}
if ( remove_actor )
{
actors_to_unregister.Remove( infos.Actor );
}

if ( infos.Group == EGBFInteractionGroup::Exclusive )
{
break;
}
}

for ( auto actor : actors_to_unregister )
{
if ( auto * context = InteractableTargetContexts.Find( actor.Get() ) )
{
context->Reset();
InteractableTargetContexts.Remove( actor.Get() );
}
}
}

void UGBFGameplayAbility_Interact::RegisterInteractions( const TArray< InteractableTargetInfos > & target_infos )
{
for ( const auto & infos : target_infos )
Expand All @@ -336,13 +301,13 @@ void UGBFGameplayAbility_Interact::RegisterInteractions( const TArray< Interacta

void UGBFGameplayAbility_Interact::RegisterInteraction( const InteractableTargetInfos & target_infos )
{
const auto * pawn = Cast< APawn >( GetAvatarActorFromActorInfo() );
const auto actor_info = GetActorInfo();

const auto * pawn = Cast< APawn >( actor_info.AvatarActor );

InteractableTargetContext context;
auto interactable_component = target_infos.InteractableComponent;

const auto & option_container = interactable_component->GetInteractableOptions();
context.InteractionsId = option_container.GetInteractionsId();

auto * asc_from_actor_info = GetAbilitySystemComponentFromActorInfo_Ensured();
auto * asc_from_interactable_target = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent( interactable_component.Get()->GetOwner() );
Expand All @@ -356,39 +321,16 @@ void UGBFGameplayAbility_Interact::RegisterInteraction( const InteractableTarget
asc_from_interactable_target->GetOwnedGameplayTags( interactable_target_tags );
}

if ( !option_container.InstigatorTagRequirements.RequirementsMet( actor_info_tags ) )
{
return;
}

if ( !option_container.InteractableTargetTagRequirements.RequirementsMet( interactable_target_tags ) )
for ( auto condition : option_container.AllowConditions )
{
return;
}

bool has_a_matching_sub_option = false;

for ( auto sub_option : option_container.GetOptions() )
{
if ( !sub_option.InstigatorTagRequirements.RequirementsMet( actor_info_tags ) )
if ( !condition->AreAllInteractionsAllowed( interactable_component.Get(), actor_info ) )
{
continue;
return;
}

if ( !sub_option.InteractableTargetTagRequirements.RequirementsMet( interactable_target_tags ) )
{
continue;
}

has_a_matching_sub_option = true;
break;
}

if ( !has_a_matching_sub_option )
{
return;
}

InteractableTargetContext context;
context.InteractionsId = option_container.GetInteractionsId();
context.WidgetInfosHandle = { interactable_component, option_container.CommonWidgetInfos };

if ( const auto * pc = Cast< APlayerController >( pawn->GetController() ) )
Expand All @@ -406,18 +348,20 @@ void UGBFGameplayAbility_Interact::RegisterInteraction( const InteractableTarget
}
}

for ( auto & option : option_container.GetOptions() )
{
if ( !option.InstigatorTagRequirements.RequirementsMet( actor_info_tags ) )
auto valid_options = option_container.GetOptions().FilterByPredicate( [ & ]( const FGBFInteractionOption & option ) {
for ( auto condition : option.AllowConditions )
{
continue;
if ( !condition->IsOptionAllowed( interactable_component.Get(), actor_info, option ) )
{
return false;
}
}

if ( !option.InteractableTargetTagRequirements.RequirementsMet( interactable_target_tags ) )
{
continue;
}
return true;
} );

for ( auto & option : valid_options )
{
OptionHandle option_handle;
option_handle.InteractableComponent = interactable_component;
option_handle.InitialInteractionOption = option;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "Interaction/GBFInteractionAllowCondition.h"

#include <AbilitySystemBlueprintLibrary.h>
#include <AbilitySystemComponent.h>

bool UGBFInteractionAllowCondition::AreAllInteractionsAllowed_Implementation( UGBFInteractableComponent * /*interactable_component*/, const FGameplayAbilityActorInfo & /*instigator_infos*/ ) const
{
return false;
}

bool UGBFInteractionAllowCondition::IsOptionAllowed_Implementation( UGBFInteractableComponent * /*interactable_component*/, const FGameplayAbilityActorInfo & /*instigator_infos */, const FGBFInteractionOption & /*option*/ ) const
{
return false;
}

bool UGBFInteractionAllowCondition_TagRequirements::AreAllInteractionsAllowed_Implementation( UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos ) const
{
FGameplayTagContainer tags;
GetGameplayTags( tags, interactable_component, instigator_infos );

return TagRequirements.RequirementsMet( tags );
}

bool UGBFInteractionAllowCondition_TagRequirements::IsOptionAllowed_Implementation( UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos, const FGBFInteractionOption & /*option*/ ) const
{
FGameplayTagContainer tags;
GetGameplayTags( tags, interactable_component, instigator_infos );

return TagRequirements.RequirementsMet( tags );
}

void UGBFInteractionAllowCondition_TagRequirements::GetGameplayTags( FGameplayTagContainer & /*tags*/, UGBFInteractableComponent * /*interactable_component*/, const FGameplayAbilityActorInfo & /*instigator_infos*/ ) const
{
}

void UGBFInteractionAllowCondition_InstigatorTags::GetGameplayTags( FGameplayTagContainer & tags, UGBFInteractableComponent * /*interactable_component*/, const FGameplayAbilityActorInfo & instigator_infos ) const
{
instigator_infos.AbilitySystemComponent->GetOwnedGameplayTags( tags );
}

void UGBFInteractionAllowCondition_InteractableActorTags::GetGameplayTags( FGameplayTagContainer & tags, UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & /*instigator_infos*/ ) const
{
if ( auto * asc = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent( interactable_component->GetOwner() ) )
{
asc->GetOwnedGameplayTags( tags );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ FGBFInteractionOptionContainer::FGBFInteractionOptionContainer( const FGBFIntera
InputMappingContext( other.InputMappingContext ),
DefaultInputAction( other.DefaultInputAction ),
InteractionGroup( other.InteractionGroup ),
InteractableTargetTagRequirements( other.InteractableTargetTagRequirements ),
InstigatorTagRequirements( other.InstigatorTagRequirements ),
CommonWidgetInfos( other.CommonWidgetInfos ),
AllowConditions( other.AllowConditions ),
Options( other.Options ),
// :NOTE: Increment the id to make sure we invalidate this container and force a full refresh of the options
InteractionsId( other.InteractionsId + 1 )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ class GAMEBASEFRAMEWORK_API UGBFGameplayAbility_Interact : public UGBFGameplayAb
void OnPressCallBack( OptionHandle interaction_option );
void UpdateIndicators();
void GatherTargetInfos( TArray< InteractableTargetInfos > & target_infos, const TArray< UGBFInteractableComponent * > & interactable_components ) const;
void ResetUnusedInteractions( const TArray< InteractableTargetInfos > & target_infos );
void RegisterInteractions( const TArray< InteractableTargetInfos > & target_infos );
void RegisterInteraction( const InteractableTargetInfos & target_infos );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

#include "GBFInteractableComponent.h"

#include <CoreMinimal.h>

#include "GBFInteractionAllowCondition.generated.h"

UCLASS( Abstract, Const, Blueprintable, DefaultToInstanced, EditInlineNew )
class GAMEBASEFRAMEWORK_API UGBFInteractionAllowCondition : public UObject
{
GENERATED_BODY()

public:
UFUNCTION( BlueprintPure, BlueprintNativeEvent )
bool AreAllInteractionsAllowed( UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos ) const;

UFUNCTION( BlueprintPure, BlueprintNativeEvent )
bool IsOptionAllowed( UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos, const FGBFInteractionOption & option ) const;
};

UCLASS( abstract )
class GAMEBASEFRAMEWORK_API UGBFInteractionAllowCondition_TagRequirements : public UGBFInteractionAllowCondition
{
public:
GENERATED_BODY()

bool AreAllInteractionsAllowed_Implementation( UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos ) const override;
bool IsOptionAllowed_Implementation( UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos, const FGBFInteractionOption & option ) const override;

protected:
virtual void GetGameplayTags( FGameplayTagContainer & tags, UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos ) const;

private:
UPROPERTY( EditAnywhere )
FGameplayTagRequirements TagRequirements;
};

UCLASS()
class GAMEBASEFRAMEWORK_API UGBFInteractionAllowCondition_InstigatorTags : public UGBFInteractionAllowCondition_TagRequirements
{
public:
GENERATED_BODY()

protected:
void GetGameplayTags( FGameplayTagContainer & tags, UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos ) const override;
};

UCLASS()
class GAMEBASEFRAMEWORK_API UGBFInteractionAllowCondition_InteractableActorTags : public UGBFInteractionAllowCondition_TagRequirements
{
public:
GENERATED_BODY()

protected:
void GetGameplayTags( FGameplayTagContainer & tags, UGBFInteractableComponent * interactable_component, const FGameplayAbilityActorInfo & instigator_infos ) const override;
};
18 changes: 6 additions & 12 deletions Source/GameBaseFramework/Public/Interaction/GBFInteractionOption.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "GBFInteractionOption.generated.h"

class UGBFInteractionAllowCondition;
class UGBFInteractionEventCustomization;
class UInputAction;
class UInputMappingContext;
Expand Down Expand Up @@ -115,11 +116,8 @@ struct FGBFInteractionOption
UPROPERTY( EditAnywhere, Instanced )
TObjectPtr< UGBFInteractionEventCustomization > EventCustomization;

UPROPERTY( EditAnywhere, BlueprintReadOnly )
FGameplayTagRequirements InteractableTargetTagRequirements;

UPROPERTY( EditAnywhere, BlueprintReadOnly )
FGameplayTagRequirements InstigatorTagRequirements;
UPROPERTY( EditAnywhere, Instanced )
TArray< TObjectPtr< UGBFInteractionAllowCondition > > AllowConditions;

UPROPERTY( EditAnywhere, BlueprintReadOnly )
TObjectPtr< const UInputAction > InputAction = nullptr;
Expand Down Expand Up @@ -166,11 +164,8 @@ struct FGBFInteractionOptionContainer
UPROPERTY( EditAnywhere, BlueprintReadOnly )
EGBFInteractionGroup InteractionGroup = EGBFInteractionGroup::Exclusive;

UPROPERTY( EditAnywhere, BlueprintReadOnly )
FGameplayTagRequirements InteractableTargetTagRequirements;

UPROPERTY( EditAnywhere, BlueprintReadOnly )
FGameplayTagRequirements InstigatorTagRequirements;
UPROPERTY( EditAnywhere, Instanced )
TArray< TObjectPtr< UGBFInteractionAllowCondition > > AllowConditions;

UPROPERTY( EditAnywhere, BlueprintReadOnly )
FGBFInteractionWidgetInfos CommonWidgetInfos;
Expand Down Expand Up @@ -199,8 +194,7 @@ FORCEINLINE FGBFInteractionOptionContainer & FGBFInteractionOptionContainer::ope
InputMappingContext = other.InputMappingContext;
DefaultInputAction = other.DefaultInputAction;
InteractionGroup = other.InteractionGroup;
InteractableTargetTagRequirements = other.InteractableTargetTagRequirements;
InstigatorTagRequirements = other.InstigatorTagRequirements;

Options = other.Options;
CommonWidgetInfos = other.CommonWidgetInfos;

Expand Down