190 lines
6.2 KiB
C++
190 lines
6.2 KiB
C++
// ©2022 Batty Bovine Productions, LLC. All Rights Reserved.
|
|
|
|
#include "Components/InputBufferComponent.h"
|
|
|
|
#include "Components/InputComponent.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogInputBufferComponent);
|
|
|
|
|
|
UInputBufferComponent::UInputBufferComponent()
|
|
{
|
|
PrimaryComponentTick.bStartWithTickEnabled = false;
|
|
PrimaryComponentTick.bTickEvenWhenPaused = false;
|
|
PrimaryComponentTick.bCanEverTick = false;
|
|
}
|
|
|
|
void UInputBufferComponent::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
// Get all unique EnhancedInput actions bound to combo input actions.
|
|
TSet<const UInputAction*> InputActionsToBind;
|
|
for (const UComboInputAsset *ComboInput : this->ComboActions)
|
|
{
|
|
for (const UInputAction *InputAction : ComboInput->ActionGroup)
|
|
{
|
|
InputActionsToBind.Add(InputAction);
|
|
}
|
|
}
|
|
|
|
APlayerController *Controller = Cast<APlayerController>(this->GetOwner());
|
|
checkf(Controller, TEXT("No player controller found as owner of %s"), *this->GetName());
|
|
|
|
if (this->EnhancedInputComponent = Cast<UEnhancedInputComponent>(Controller->InputComponent))
|
|
{
|
|
// Bind the input actions we found to the buffer management functions.
|
|
for (const UInputAction *InputAction : InputActionsToBind)
|
|
{
|
|
this->EnhancedInputComponent->BindAction(InputAction, ETriggerEvent::Started, this, &UInputBufferComponent::AddActionToBuffer, InputAction);
|
|
this->EnhancedInputComponent->BindAction(InputAction, ETriggerEvent::Completed, this, &UInputBufferComponent::ExpireAction, InputAction);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogInputBufferComponent, Error, TEXT("Parent of %s is not a UEnhancedInputComponent type."), *this->GetName());
|
|
}
|
|
}
|
|
|
|
|
|
void UInputBufferComponent::AddActionToBuffer(const FInputActionValue &Value, const class UInputAction *Action)
|
|
{
|
|
this->MostRecentActions.Add(Action);
|
|
this->ExpiringActions.Remove(Action);
|
|
|
|
// Find any combo input that matches this action, plus buffered actions.
|
|
for (const UComboInputAsset *Combo : this->ComboActions)
|
|
{
|
|
if (Combo->MatchesInputActions(this->MostRecentActions))
|
|
{
|
|
this->ActivateComboInput(Combo);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
this->GetWorld()->GetTimerManager().SetTimer(this->MultiPressTimerHandle, this, &UInputBufferComponent::ClearMultiPresses, this->MultiPressTimerLength);
|
|
}
|
|
void UInputBufferComponent::ClearMultiPresses()
|
|
{
|
|
#if WITH_EDITOR
|
|
TArray<FString> ActionNames;
|
|
for (const UInputAction *Action : this->MostRecentActions)
|
|
{
|
|
ActionNames.Add(Action->GetName());
|
|
}
|
|
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Multi-press buffer cleared (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
|
|
#endif
|
|
this->MostRecentActions.Empty();
|
|
}
|
|
|
|
void UInputBufferComponent::ActivateComboInput(const UComboInputAsset *ComboInput)
|
|
{
|
|
checkf(ComboInput, TEXT("Invalid UComboInputAsset"));
|
|
|
|
// Make this combo input active if it isn't being locked, or if we are
|
|
// overwriting a previous combo input with a multi-press combo input.
|
|
const bool bMultiPressTimerActive = this->GetWorld()->GetTimerManager().IsTimerActive(this->MultiPressTimerHandle);
|
|
const bool bComboInputLocked = this->LockedComboInputs.Contains(ComboInput);
|
|
if (bMultiPressTimerActive || !bComboInputLocked)
|
|
{
|
|
if (!ComboInput->LockedComboInputs.IsEmpty())
|
|
{
|
|
// Set the combo input as active, and copy its lock data.
|
|
this->InputBufferActive = ComboInput;
|
|
this->LockedComboInputs = ComboInput->LockedComboInputs;
|
|
|
|
// Make sure the hold is clear if we're coming off of a multi-press action.
|
|
if (bMultiPressTimerActive)
|
|
{
|
|
this->InputBufferHold = nullptr;
|
|
}
|
|
|
|
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is active."), *ComboInput->ComboInputName.ToString());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is active and won't lock inputs."), *ComboInput->ComboInputName.ToString());
|
|
}
|
|
|
|
this->NewComboInput.Execute(ComboInput);
|
|
}
|
|
else
|
|
{
|
|
this->InputBufferHold = ComboInput;
|
|
|
|
if (bComboInputLocked)
|
|
{
|
|
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is locked and won't be activated yet."), *ComboInput->ComboInputName.ToString());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s added to buffer."), *ComboInput->ComboInputName.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void UInputBufferComponent::UnlockComboInput(const UComboInputAsset *Unlocked)
|
|
{
|
|
// Remove the newly-unlocked asset from the locked combo inputs.
|
|
UE_CLOG(this->LockedComboInputs.Contains(Unlocked), LogInputBufferComponent, Verbose, TEXT("%s has unlocked."), *Unlocked->ComboInputName.ToString());
|
|
this->LockedComboInputs.Remove(Unlocked);
|
|
|
|
// Check if the newly unlocked combo input is in the hold.
|
|
if (Unlocked == this->InputBufferHold)
|
|
{
|
|
const UComboInputAsset *OriginalActive = this->InputBufferActive;
|
|
|
|
// Activate the held combo input.
|
|
const UComboInputAsset *HeldAsset = this->InputBufferHold;
|
|
this->InputBufferHold = nullptr;
|
|
if (HeldAsset)
|
|
{
|
|
this->ActivateComboInput(HeldAsset);
|
|
}
|
|
|
|
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s has expired."), *OriginalActive->ComboInputName.ToString());
|
|
}
|
|
}
|
|
|
|
void UInputBufferComponent::ExpireAction(const FInputActionValue &Value, const class UInputAction *Action)
|
|
{
|
|
this->ExpiringActions.Add(Action);
|
|
this->GetWorld()->GetTimerManager().SetTimer(this->InputReleaseExpirationTimerHandle, this, &UInputBufferComponent::ExpireBufferedActions, this->InputReleaseExpirationTimerLength);
|
|
}
|
|
void UInputBufferComponent::ExpireBufferedActions()
|
|
{
|
|
// Only bother dealing with this if there's something to deal with in the first place.
|
|
if (!this->ExpiringActions.IsEmpty())
|
|
{
|
|
UE_SUPPRESS(LogInputBufferComponent, Verbose,
|
|
{
|
|
TArray<FString> ActionNames;
|
|
for (const UInputAction *Action : this->ExpiringActions)
|
|
{
|
|
ActionNames.Add(Action->GetName());
|
|
}
|
|
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Released actions expired (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
|
|
}
|
|
);
|
|
|
|
// If there is an action in the hold, check if it's related
|
|
// to our current released buttons, and if so cancel it.
|
|
if (this->InputBufferHold)
|
|
{
|
|
for (const UInputAction *Action : this->ExpiringActions)
|
|
{
|
|
if (this->InputBufferHold->MatchesInputAction(Action))
|
|
{
|
|
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s has been cancelled."), *this->InputBufferHold->ComboInputName.ToString());
|
|
this->InputBufferHold = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
this->ExpiringActions.Empty();
|
|
}
|
|
}
|