diff --git a/Source/UnrealFightingFramework/IFFSystemInterface.cpp b/Source/UnrealFightingFramework/IFFSystemInterface.cpp deleted file mode 100644 index 7be46d6..0000000 --- a/Source/UnrealFightingFramework/IFFSystemInterface.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// Unreal Fighting Framework by Kevin Poretti - - -#include "IFFSystemInterface.h" diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 9d40713..0401d1f 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -33,6 +33,19 @@ void AFFPlayerController::FixedTick(float OneFrame) } +bool AFFPlayerController::CheckInputSequences(const TArray& InputSequences) +{ + for(const FFFInputSequence& ThisInputSequence : InputSequences) + { + if(InputBuffer->CheckInputSequence(ThisInputSequence)) + { + return true; + } + } + return false; +} + + void AFFPlayerController::SetupInputComponent() { Super::SetupInputComponent(); diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index ded1695..b509246 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -6,6 +6,7 @@ #include "FFInputBufferComponent.h" #include "IFFSystemInterface.h" #include "Utils/TCircleBuffer.h" +#include "State/IFFStateOwnerInterface.h" // UE includes #include "CoreMinimal.h" @@ -19,7 +20,7 @@ * unacknowledged inputs to a remote client or server for processing. */ UCLASS() -class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController, public IFFSystemInterface +class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController, public IFFSystemInterface, public IFFStateOwnerInterface { GENERATED_BODY() @@ -50,6 +51,10 @@ public: virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface + // IFFStateOwnerInterface + virtual bool CheckInputSequences(const TArray& InputSequences) override; + // End of IFFStateOwnerInterface + // APlayerController interface virtual void SetupInputComponent() override; // End of APlayerController interface diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index a06214a..a8491aa 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -4,61 +4,97 @@ // FF includes #include "FFStateMachineComponent.h" +#include "IFFStateAvatarInterface.h" +#include "IFFStateOwnerInterface.h" +// UE includes +#include "Components/SkeletalMeshComponent.h" -void UFFStateBehavior::Enter(const FFFStateContext& InStateContext) +void UFFState::Enter(const FFFStateContext& InStateContext) { + if(InStateContext.Avatar) + { + USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); + if(SMC && MontageToPlay) + { + SMC->GetAnimInstance()->Montage_Play(MontageToPlay); + } + } + OnEnter(InStateContext); } -void UFFStateBehavior::Exit(const FFFStateContext& InStateContext) +void UFFState::Exit(const FFFStateContext& InStateContext) { + if(InStateContext.Avatar) + { + USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); + if(SMC && MontageToPlay) + { + FAlphaBlendArgs BlendOutArgs = MontageToPlay->GetBlendOutArgs(); + SMC->GetAnimInstance()->Montage_Stop(BlendOutArgs.BlendTime, MontageToPlay); + } + } + OnExit(InStateContext); } -void UFFStateBehavior::Update(float OneFrame, const FFFStateContext& InStateContext) +void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) { OnUpdate(OneFrame, InStateContext); } -void UFFStateBehavior::OnLanded_Implementation(const FFFStateContext& InStateContext) + +bool UFFState::CanTransition_Implementation(const FFFStateContext& InStateContext) { + /** + * Check to see if avatar implements StateAvatarInterface + * if so then + * Check if the avatar is in the correct stance to perform this action + * Check if the state is enabled + * Check that all state entry conditions are met if there are any + * + * Check to see if the owner implements the StateOwnerInterface + * Check input conditions if there are any + * + * If all state entry conditions are good and at least one input condition is good then we can transition + * so return true otherwise return false + */ + IFFStateAvatarInterface* SAI = Cast(InStateContext.Avatar); + if(SAI) + { + if(!(SAI->CheckStance(StanceRequired) && SAI->CheckStateEnabled(StateType) && SAI->CheckStateEntryConditions(EntryConditions))) + { + return false; + } + } + + IFFStateOwnerInterface* SOI = Cast(InStateContext.Owner); + if(SOI) + { + if(!SOI->CheckInputSequences(InputSequences)) + { + return false; + } + } + + return true; } -void UFFStateBehavior::OnEnter_Implementation(const FFFStateContext& InStateContext) +void UFFState::GotoSubState(FName InSubStateLabel) { + UFFStateMachineComponent* SMC = Cast(GetOuter()); + if(SMC) + { + SMC->SetSubStateLabel(InSubStateLabel); + } } -void UFFStateBehavior::OnExit_Implementation(const FFFStateContext& InStateContext) -{ -} - - -void UFFStateBehavior::OnUpdate_Implementation(float OneFrame, const FFFStateContext& InStateContext) -{ -} - - -void UFFStateBehavior::OnHit_Implementation(const FFFStateContext& InStateContext) -{ -} - - -void UFFStateBehavior::OnBlock_Implementation(const FFFStateContext& InStateContext) -{ -} - - -void UFFStateBehavior::OnInputEvent_Implementation(const FFFStateContext& InStateContext) -{ -} - - -UWorld* UFFStateBehavior::GetWorld() const +UWorld* UFFState::GetWorld() const { UFFStateMachineComponent* SMC = Cast(GetOuter()); if(SMC) diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index c851b80..d02282f 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -2,29 +2,14 @@ #pragma once +// FF includes +#include "Input/FFInputBufferComponent.h" + // UE includes #include "CoreMinimal.h" #include "FFState.generated.h" -USTRUCT(BlueprintType) -struct FFFStateData -{ - GENERATED_BODY() - - /** Name of this state */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") - FName Name; - - /** Conditions that need to be met in order for this state to be transitioned into */ - UPROPERTY(EditAnywhere) - TArray EntryConditions; - - /** What is this state's category. Used for determining what types of state can prematurely cancel this one. */ - UPROPERTY(EditAnywhere) - uint8 StateType; - -}; USTRUCT(BlueprintType) struct FFFStateContext @@ -40,11 +25,13 @@ struct FFFStateContext */ AActor* Avatar; - /** - * Data associated with this state. - * For example this can be new movement values or data about the hitboxes if this state represents an attack. - */ - FFFStateData StateData; + /** + * Parent state machine that controls this state + */ + const class UFFStateMachineComponent* Parent; + + /** The number of ticks that have elapsed since this state was entered into */ + int64 TicksInState; }; /** @@ -52,68 +39,132 @@ struct FFFStateContext * and logic to run when the state is entered, exited, and active. */ UCLASS() -class UNREALFIGHTINGFRAMEWORK_API UFFStateBehavior : public UObject +class UNREALFIGHTINGFRAMEWORK_API UFFState : public UObject { GENERATED_BODY() public: + // State parameters (should be read-only) /** Name of this state behavior */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") FName Name; - // TODO: since state's are now purely behavioral can we remove these function calls? - // They are basically redundant with OnEnter, OnUpdate, OnExit, etc. /** + * How long this state will be active before finishing if this state is not cancelled out of + * by other means. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + int64 StateDuration; + + /** + * What stance the object this state represents must be in. + * For example this is usually an enumerated value that signifies if a character needs to be + * crouching, standing, or airborne for this state to be eligible for transitioning. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + uint8 StanceRequired; + + /** + * What is this state's category. + * Used for determining what types of state can prematurely cancel this one. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + uint8 StateType; + + /** Conditions that need to be met in order for this state to be transitioned into */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + TArray EntryConditions; + + /** + * Input sequences that needs to be present in the controlling player's input buffer for this + * state to be eligible for transitioning. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + TArray InputSequences; + + /** + * If true the state can transition from itself into itself without having to go through + * another state like Idle + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + bool bCanTransitionToSelf = false; + + /** + * Animation to begin playing when this state is entered + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + UAnimMontage* MontageToPlay; + + + + /** + * Returns true if Avatar's is in the correct stance AND + * the state type is enabled (or the state can be hit or whiff cancelled from the current state) AND + * all state entry conditions were met AND + * at least one input sequence is present in the Owner's input buffer + */ + UFUNCTION(BlueprintNativeEvent, Category="UFF|State") + bool CanTransition(const FFFStateContext& InStateContext); + + /** + * Sets the current SubState label on the state machine that controls this + */ + UFUNCTION(BlueprintCallable, Category="UFF|State") + void GotoSubState(FName InSubStateLabel); + + /** * Called whenever this state is transitioned into. * - * Resets TicksInState and calls appropriate Blueprint hooks + * Calls appropriate Blueprint hooks. */ - void Enter(const FFFStateContext& InStateContext); + virtual void Enter(const FFFStateContext& InStateContext); - /** + /** * Called whenever this state is transitioned out of into a new state. + * + * Calls appropriate Blueprint hooks. */ - void Exit(const FFFStateContext& InStateContext); + virtual void Exit(const FFFStateContext& InStateContext); - /** + /** * Called whenever this state is active and the game logic ticks. * - * Increments TicksInState and calls appropriate Blueprint hooks. + * Calls appropriate Blueprint hooks. * * @param OneFrame the time that elapses during one fixed tick */ - void Update(float OneFrame, const FFFStateContext& InStateContext); + virtual void Update(float OneFrame, const FFFStateContext& InStateContext); /** - * Blueprint hook that is called whenever this state is transitioned into + * Blueprint hook for whenever this state is transitioned into */ - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnEnter(const FFFStateContext& InStateContext); /** - * Blueprint hook that is called whenever this state is transitioned out of into a new state + * Blueprint hook for whenever this state is transitioned out of into a new state */ - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnExit(const FFFStateContext& InStateContext); /** - * Blueprint hook that is called whenever this state is active and the game logic ticks + * Blueprint hook for whenever this state is active and the game logic ticks. * * @param OneFrame the time that elapses during one fixed tick */ - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnUpdate(float OneFrame, const FFFStateContext& InStateContext); - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnLanded(const FFFStateContext& InStateContext); - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnHit(const FFFStateContext& InStateContext); - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnBlock(const FFFStateContext& InStateContext); - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnInputEvent(const FFFStateContext& InStateContext); // UObject interface diff --git a/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp b/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp deleted file mode 100644 index 045d032..0000000 --- a/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// Unreal Fighting Framework by Kevin Poretti - - -#include "State/FFStateContextInterface.h" - -// Add default functionality here for any IFFStateContextInterface functions that are not pure virtual. diff --git a/Source/UnrealFightingFramework/State/FFStateData.h b/Source/UnrealFightingFramework/State/FFStateData.h new file mode 100644 index 0000000..6452943 --- /dev/null +++ b/Source/UnrealFightingFramework/State/FFStateData.h @@ -0,0 +1,25 @@ +// Unreal Fighting Framework by Kevin Poretti + +#pragma once + +// FF includes +#include "State/FFState.h" + +// UE includes +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" + +#include "FFStateData.generated.h" + +/** + * + */ +UCLASS() +class UNREALFIGHTINGFRAMEWORK_API UFFStateData : public UDataAsset +{ + GENERATED_BODY() + +public: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + TArray> States; +}; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 42d19aa..5b8c77c 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -14,9 +14,9 @@ UFFStateMachineComponent::UFFStateMachineComponent() void UFFStateMachineComponent::Initialize() { - for(const TSubclassOf& CurrState : DefaultStates) + for(const TSubclassOf& CurrState : DefaultStates) { - UFFStateBehavior* TempState = AddState(CurrState); + UFFState* TempState = AddState(CurrState); if(!CurrentState) // first state to be created is the entry into this state machine { CurrentState = TempState; @@ -33,9 +33,9 @@ void UFFStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar) } -UFFStateBehavior* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) +UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) { - UFFStateBehavior* TempState = NewObject(this, StateClassToAdd); + UFFState* TempState = NewObject(this, StateClassToAdd); if(TempState) { States.Add(TempState); @@ -46,9 +46,9 @@ UFFStateBehavior* UFFStateMachineComponent::AddState(TSubclassOf>& StateClassesToAdd) +void UFFStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) { - for(const TSubclassOf& CurrState : StateClassesToAdd) + for(const TSubclassOf& CurrState : StateClassesToAdd) { AddState(CurrState); } @@ -60,21 +60,24 @@ void UFFStateMachineComponent::RemoveState(FName StateToRemove) UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); } -void UFFStateMachineComponent::SwitchStates(UFFStateBehavior* NewState) +void UFFStateMachineComponent::SwitchStates(UFFState* NewState) { + check(CurrentState); check(NewState); - CurrentState->Exit(GetCurrentStateContext()); CurrentState = NewState; CurrentState->Enter(GetCurrentStateContext()); TicksInState = 0; + CurrentSubStateLabel = NAME_None; } FName UFFStateMachineComponent::GetCurrentStateName() const { - return CurrentState ? CurrentState->Name : NAME_None; + check(CurrentState) + + return CurrentState->Name; } @@ -83,13 +86,21 @@ FFFStateContext UFFStateMachineComponent::GetCurrentStateContext() const FFFStateContext CurrStateContext; CurrStateContext.Owner = Owner; CurrStateContext.Avatar = Avatar; + CurrStateContext.Parent = this; + CurrStateContext.TicksInState = TicksInState; return CurrStateContext; } -UFFStateBehavior* UFFStateMachineComponent::FindStateWithName(FName StateName) +void UFFStateMachineComponent::SetSubStateLabel(FName InSubStateLabel) { - for (UFFStateBehavior* CurrState : States) + CurrentSubStateLabel = InSubStateLabel; +} + + +UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) +{ + for (UFFState* CurrState : States) { if(CurrState->Name == StateName) { @@ -114,32 +125,35 @@ void UFFStateMachineComponent::BeginPlay() void UFFStateMachineComponent::FixedTick(float OneFrame) { - // Should we switch states? - - for(UFFStateBehavior* CurrState : States) - { - // Check if the state is enabled - - // Check state entry conditions if there are any - - // Check input conditions if there are any - - // If all state entry conditions are good and at least one input condition is good then we can transition - - // Lastly just check if the state we're about to transition into isn't the current state. - // It is OK to transition if state's "CanTransitionToSelf" is true - - // SwitchStates(NewState); - // return; - } - // CurrentState should never be null // TODO: Should probably assert or whatever UE's equivalent is check(CurrentState); - // Tick current state - TicksInState++; - CurrentState->Update(OneFrame, GetCurrentStateContext()); + // Should we switch states? + UFFState* StateToTransitionTo = nullptr; + for(UFFState* ThisState : States) + { + // found a state + if(ThisState->CanTransition(GetCurrentStateContext())) + { + StateToTransitionTo = ThisState; + break; + } + } + + // Lastly just check if the state we're about to transition into isn't the current state. + // It is OK to transition if state's "CanTransitionToSelf" is true + if(StateToTransitionTo && + (CurrentState->Name != StateToTransitionTo->Name || StateToTransitionTo->bCanTransitionToSelf)) + { + SwitchStates(StateToTransitionTo); + } + else + { + // Tick current state + TicksInState++; + CurrentState->Update(OneFrame, GetCurrentStateContext()); + } // Debug } diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 96f371d..9597c54 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -3,7 +3,8 @@ #pragma once // FF includes -#include "UnrealFightingFramework/IFFSystemInterface.h" +#include "IFFSystemInterface.h" +#include "FFState.h" // UE includes #include "CoreMinimal.h" @@ -47,14 +48,14 @@ public: * * @return A pointer to the state that was added or nullptr if there was an issue adding or creating the state */ - UFFStateBehavior* AddState(TSubclassOf StateClassToAdd); + UFFState* AddState(TSubclassOf StateClassToAdd); /** * Creates an instance of the state classes and adds newly created states to this state machine. * * @param StateClassesToAdd Array of state class types to be added to this state machine */ - void AddStates(const TArray>& StateClassesToAdd); + void AddStates(const TArray>& StateClassesToAdd); /** * Destroys the state with corresponding name and removes it from this state machine. @@ -66,7 +67,7 @@ public: * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state */ - void SwitchStates(UFFStateBehavior* NewState); + void SwitchStates(UFFState* NewState); /** * Returns the name of the current state @@ -79,6 +80,8 @@ public: */ FFFStateContext GetCurrentStateContext() const; + void SetSubStateLabel(FName InSubStateLabel); + // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface interface @@ -100,26 +103,30 @@ protected: /** How many ticks have elapsed since the currently active state was entered */ UPROPERTY(BlueprintReadOnly, Category="UFF|State") - int32 TicksInState; + int64 TicksInState; /** * States classes to create and add to this state machine when the game starts */ UPROPERTY(EditDefaultsOnly, Category="UFF|State Machine") - TArray> DefaultStates; + TArray> DefaultStates; /** Current active state for this state machine */ UPROPERTY(BlueprintReadOnly) - UFFStateBehavior* CurrentState; + UFFState* CurrentState; + + /** Current SubState label or NAME_None if there is no SubState label set for the current state*/ + UPROPERTY(BlueprintReadOnly) + FName CurrentSubStateLabel; // States that have been added UPROPERTY(BlueprintReadOnly) - TArray States; + TArray States; /** * Returns the state with corresponding name */ - UFFStateBehavior* FindStateWithName(FName StateName); + UFFState* FindStateWithName(FName StateName); // UActorComponent interface virtual void BeginPlay() override; diff --git a/Source/UnrealFightingFramework/State/FFStateContextInterface.h b/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h similarity index 53% rename from Source/UnrealFightingFramework/State/FFStateContextInterface.h rename to Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h index a797aad..7b96379 100644 --- a/Source/UnrealFightingFramework/State/FFStateContextInterface.h +++ b/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h @@ -5,10 +5,10 @@ #include "CoreMinimal.h" #include "UObject/Interface.h" -#include "FFStateContextInterface.generated.h" +#include "IFFStateAvatarInterface.generated.h" UINTERFACE(MinimalAPI) -class UFFStateContextInterface : public UInterface +class UFFStateAvatarInterface : public UInterface { GENERATED_BODY() }; @@ -16,16 +16,14 @@ class UFFStateContextInterface : public UInterface /** * */ -class UNREALFIGHTINGFRAMEWORK_API IFFStateContextInterface +class UNREALFIGHTINGFRAMEWORK_API IFFStateAvatarInterface { GENERATED_BODY() public: - virtual bool CheckStateEnabled(uint8 StateType) = 0; - virtual bool CheckStance(uint8 Stance) = 0; - virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; + virtual bool CheckStateEnabled(uint8 StateType) = 0; - virtual bool CheckInputSequences(const TArray& InputSequences) = 0; + virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; }; diff --git a/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h new file mode 100644 index 0000000..8004cf2 --- /dev/null +++ b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h @@ -0,0 +1,25 @@ +// Unreal Fighting Framework by Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "IFFStateOwnerInterface.generated.h" + +UINTERFACE(MinimalAPI) +class UFFStateOwnerInterface : public UInterface +{ + GENERATED_BODY() +}; + +/** + * + */ +class UNREALFIGHTINGFRAMEWORK_API IFFStateOwnerInterface +{ + GENERATED_BODY() + +public: + virtual bool CheckInputSequences(const TArray& InputSequences) = 0; +};